Cela ne sera pas une réponse complète à votre question, mais j'espère que cela vous aidera, vous et d'autres personnes, lorsque vous essayez de lire la documentation sur le $q
service. Il m'a fallu un certain temps pour le comprendre.
Laissons AngularJS de côté pendant un moment et considérons simplement les appels d'API Facebook. Les deux appels d'API utilisent un mécanisme de rappel pour notifier l'appelant lorsque la réponse de Facebook est disponible:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
Il s'agit d'un modèle standard pour gérer les opérations asynchrones en JavaScript et dans d'autres langages.
Un gros problème avec ce modèle se pose lorsque vous devez effectuer une séquence d'opérations asynchrones, où chaque opération successive dépend du résultat de l'opération précédente. C'est ce que fait ce code:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
Tout d'abord, il essaie de se connecter, puis ce n'est qu'après avoir vérifié que la connexion a réussi qu'il fait la demande à l'API Graph.
Même dans ce cas, qui n'enchaîne que deux opérations, les choses commencent à se compliquer. La méthode askFacebookForAuthentication
accepte un rappel en cas d'échec et de réussite, mais que se passe-t-il lorsque FB.login
réussit mais FB.api
échoue? Cette méthode appelle toujours le success
rappel quel que soit le résultat de la FB.api
méthode.
Imaginez maintenant que vous essayez de coder une séquence robuste de trois opérations asynchrones ou plus, d'une manière qui gère correctement les erreurs à chaque étape et sera lisible par n'importe qui d'autre ou même par vous après quelques semaines. Possible, mais il est très facile de continuer à imbriquer ces rappels et de perdre la trace des erreurs en cours de route.
Maintenant, laissons de côté l'API Facebook pendant un moment et considérons simplement l'API Angular Promises, telle qu'implémentée par le $q
service. Le modèle implémenté par ce service est une tentative de transformer la programmation asynchrone en quelque chose qui ressemble à une série linéaire d'instructions simples, avec la possibilité de `` lancer '' une erreur à n'importe quelle étape du processus et de la gérer à la fin, sémantiquement similaire au try/catch
bloc familier .
Considérez cet exemple artificiel. Disons que nous avons deux fonctions, où la deuxième fonction consomme le résultat de la première:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
Imaginez maintenant que firstFn et secondFn prennent tous les deux du temps à se terminer, nous voulons donc traiter cette séquence de manière asynchrone. Nous créons d'abord un nouvel deferred
objet, qui représente une chaîne d'opérations:
var deferred = $q.defer();
var promise = deferred.promise;
La promise
propriété représente le résultat final de la chaîne. Si vous enregistrez une promesse immédiatement après l'avoir créée, vous verrez qu'il ne s'agit que d'un objet vide ( {}
). Rien à voir encore, avancez tout de suite.
Jusqu'à présent, notre promesse ne représente que le point de départ de la chaîne. Ajoutons maintenant nos deux opérations:
promise = promise.then(firstFn).then(secondFn);
La then
méthode ajoute une étape à la chaîne, puis retourne une nouvelle promesse représentant le résultat final de la chaîne étendue. Vous pouvez ajouter autant d'étapes que vous le souhaitez.
Jusqu'à présent, nous avons mis en place notre chaîne de fonctions, mais rien ne s'est réellement passé. Vous démarrez les choses en appelant deferred.resolve
, en spécifiant la valeur initiale que vous souhaitez passer à la première étape réelle de la chaîne:
deferred.resolve('initial value');
Et puis ... il ne se passe toujours rien. Pour s'assurer que les changements de modèle sont correctement observés, Angular n'appelle pas réellement la première étape de la chaîne jusqu'à ce que la prochaine fois $apply
soit appelée:
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
Alors qu'en est-il de la gestion des erreurs? Jusqu'à présent, nous n'avons spécifié qu'un gestionnaire de succès à chaque étape de la chaîne. then
accepte également un gestionnaire d'erreurs comme second argument facultatif. Voici un autre exemple plus long de chaîne de promesses, cette fois avec gestion des erreurs:
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);
Comme vous pouvez le voir dans cet exemple, chaque gestionnaire de la chaîne a la possibilité de détourner le trafic vers le prochain gestionnaire d' erreur au lieu du prochain succès gestionnaire de . Dans la plupart des cas, vous pouvez avoir un seul gestionnaire d'erreurs à la fin de la chaîne, mais vous pouvez également avoir des gestionnaires d'erreurs intermédiaires qui tentent de récupérer.
Pour revenir rapidement à vos exemples (et à vos questions), je dirai simplement qu'ils représentent deux façons différentes d'adapter l'API orientée callback de Facebook à la façon dont Angular observe les changements de modèle. Le premier exemple encapsule l'appel d'API dans une promesse, qui peut être ajoutée à une portée et est comprise par le système de modèles d'Angular. Le second adopte l'approche plus brutale consistant à définir le résultat du rappel directement sur la portée, puis à appeler $scope.$digest()
pour informer Angular du changement depuis une source externe.
Les deux exemples ne sont pas directement comparables, car le premier ne comprend pas l'étape de connexion. Cependant, il est généralement souhaitable d'encapsuler les interactions avec des API externes comme celle-ci dans des services séparés et de fournir les résultats aux contrôleurs comme des promesses. De cette façon, vous pouvez séparer vos contrôleurs des préoccupations externes et les tester plus facilement avec des services simulés.