Questa non sarà una risposta completa alla tua domanda, ma spero che questo possa aiutare te e gli altri quando provi a leggere la documentazione sul $q
servizio. Mi ci è voluto un po 'per capirlo.
Mettiamo da parte AngularJS per un momento e consideriamo solo le chiamate API di Facebook. Entrambe le chiamate API utilizzano un meccanismo di richiamata per avvisare il chiamante quando è disponibile la risposta da Facebook:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
Questo è un modello standard per la gestione di operazioni asincrone in JavaScript e altre lingue.
Un grosso problema con questo modello si presenta quando è necessario eseguire una sequenza di operazioni asincrone, in cui ogni operazione successiva dipende dal risultato dell'operazione precedente. Ecco cosa sta facendo questo codice:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
In primo luogo tenta di accedere, quindi solo dopo aver verificato che l'accesso abbia avuto esito positivo invia la richiesta all'API Graph.
Anche in questo caso, che sta concatenando solo due operazioni, le cose iniziano a diventare confuse. Il metodo askFacebookForAuthentication
accetta un callback per fallimento e successo, ma cosa succede quando FB.login
riesce ma FB.api
fallisce? Questo metodo richiama sempre il success
callback indipendentemente dal risultato del FB.api
metodo.
Ora immagina di provare a codificare una sequenza solida di tre o più operazioni asincrone, in un modo che gestisca correttamente gli errori in ogni fase e sarà leggibile da chiunque o anche da te dopo poche settimane. Possibile, ma è molto semplice continuare a annidare quei callback e perdere la traccia degli errori lungo la strada.
Ora, mettiamo da parte l'API di Facebook per un momento e consideriamo solo l'API Angular Promises, come implementata dal $q
servizio. Il modello implementato da questo servizio è un tentativo di trasformare la programmazione asincrona in qualcosa che assomiglia a una serie lineare di semplici istruzioni, con la possibilità di "gettare" un errore in qualsiasi fase del percorso e gestirlo alla fine, semanticamente simile al try/catch
blocco familiare .
Considera questo esempio inventato. Supponiamo di avere due funzioni, in cui la seconda funzione consuma il risultato della prima:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
Ora immagina che firstFn e secondFn impieghino molto tempo per essere completati, quindi vogliamo elaborare questa sequenza in modo asincrono. Innanzitutto creiamo un nuovo deferred
oggetto, che rappresenta una catena di operazioni:
var deferred = $q.defer();
var promise = deferred.promise;
La promise
proprietà rappresenta l'eventuale risultato della catena. Se registri una promessa subito dopo averla creata, vedrai che è solo un oggetto vuoto ({}
). Niente da vedere ancora, muoviti subito.
Finora la nostra promessa rappresenta solo il punto di partenza nella catena. Ora aggiungiamo le nostre due operazioni:
promise = promise.then(firstFn).then(secondFn);
Il then
metodo aggiunge un passaggio alla catena e quindi restituisce una nuova promessa che rappresenta il risultato finale della catena estesa. Puoi aggiungere tutti i passaggi che desideri.
Finora abbiamo creato la nostra catena di funzioni, ma in realtà non è successo nulla. Puoi iniziare le cose chiamando deferred.resolve
, specificando il valore iniziale che vuoi passare al primo passo effettivo nella catena:
deferred.resolve('initial value');
E poi ... non succede ancora niente. Per garantire che le modifiche al modello vengano osservate correttamente, Angular non chiama in realtà il primo passo della catena fino alla successiva $apply
chiamata:
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
E la gestione degli errori? Finora abbiamo specificato solo un gestore di successo in ogni fase della catena. then
accetta anche un gestore di errori come secondo argomento facoltativo. Ecco un altro esempio più lungo di una catena di promesse, questa volta con la gestione degli errori:
var firstFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'firstResult';
}
};
var secondFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'secondResult';
}
};
var thirdFn = function(param) {
// do something with param
return 'thirdResult';
};
var errorFn = function(message) {
// handle error
};
var deferred = $q.defer();
var promise = deferred.promise.then(firstFn).then(secondFn).then(thirdFn, errorFn);
Come puoi vedere in questo esempio, ciascun gestore della catena ha l'opportunità di deviare il traffico al gestore degli errori successivo anziché al gestore dei successi successivo . Nella maggior parte dei casi è possibile avere un singolo gestore errori alla fine della catena, ma è anche possibile avere gestori errori intermedi che tentano il ripristino.
Per tornare rapidamente ai tuoi esempi (e alle tue domande), dirò solo che rappresentano due modi diversi per adattare l'API orientata alla callback di Facebook al modo di Angular di osservare i cambiamenti del modello. Il primo esempio racchiude la chiamata API in una promessa, che può essere aggiunta a un ambito ed è compresa dal sistema di modelli di Angular. Il secondo utilizza l'approccio più bruto di impostare il risultato della richiamata direttamente sull'ambito e quindi chiamare $scope.$digest()
per rendere Angular consapevole del cambiamento da una fonte esterna.
I due esempi non sono direttamente confrontabili, poiché al primo manca il passaggio di accesso. Tuttavia, è generalmente desiderabile incapsulare interazioni con API esterne come questa in servizi separati e fornire i risultati ai controller come promesso. In questo modo è possibile mantenere i controller separati da preoccupazioni esterne e testarli più facilmente con servizi simulati.