Ce n'est pas une réponse complète à votre question, mais j'espère que cela va vous aider, vous et d'autres personnes lorsque vous essayez de lire la documentation sur l' $q
de service. Il m'a fallu un certain temps pour le comprendre.
Laissons de côté AngularJS pour un moment, et il suffit de considérer le Facebook des appels d'API. À la fois les appels d'API utiliser un rappel de mécanisme pour informer 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
...
C'est un modèle standard pour la manipulation des opérations asynchrones en JavaScript et d'autres langages.
Un gros problème avec ce modèle se pose lorsque vous avez besoin pour effectuer une séquence d'opérations asynchrones, où chaque opération dépend du résultat de l'opération précédente. C'est ce que ce code est en train de faire:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
D'abord, il essaie de se connecter, puis seulement après avoir vérifié que la connexion a réussi t-il en faire la demande à l'API Graphique.
Même dans ce cas, qui est le seul à enchaîner deux opérations, les choses commencent à déraper. La méthode askFacebookForAuthentication
accepte un rappel de l'échec et de la réussite, mais ce qui arrive quand FB.login
réussit, mais FB.api
d'échec? Cette méthode appelle toujours l' success
rappel quel que soit le résultat de l' FB.api
méthode.
Maintenant, imaginez que vous êtes en train de coder un robuste séquence de trois ou plus des opérations asynchrones, d'une manière qui gère correctement les erreurs à chaque étape et sera lisible pour quelqu'un d'autre ou même après quelques semaines. Possible, mais il est très facile de simplement continuer à la nidification de ces rappels et de perdre la trace des erreurs le long du chemin.
Maintenant, laissons de côté l'Facebook API, pour un moment, et il suffit de considérer l'angle de Promesses API, mis en oeuvre par l' $q
de service. Le modèle mis en œuvre par ce service est une tentative de tourner la programmation asynchrone en arrière en quelque chose ressemblant à une série linéaire de déclarations simples, avec la possibilité de lancer une erreur à n'importe quelle étape de la voie et de le traiter à la fin, sémantiquement similaires aux familiers try/catch
bloc.
Considérons cet exemple artificiel. Disons que nous avons deux fonctions, d'où la deuxième fonction qui 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());
Maintenant, imaginez que firstFn et secondFn ont tous deux une longue période de temps, si l'on veut traiter cette séquence de façon asynchrone. Nous avons d'abord créer un nouveau deferred
de l'objet, ce qui représente une chaîne d'opérations:
var deferred = $q.defer();
var promise = deferred.promise;
L' promise
propriété représente le résultat final de la chaîne. Si vous vous connectez une promesse immédiatement après la création, vous allez voir que c'est juste un objet vide ({}
). Rien à voir pourtant, aller à droite le long.
Jusqu'à présent notre promesse ne représente que le point de départ de la chaîne. Maintenant, nous allons ajouter nos deux opérations:
promise = promise.then(firstFn).then(secondFn);
L' then
méthode ajoute une étape à la chaîne et renvoie une nouvelle promesse représentant le résultat final de l'extension de la chaîne. 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 de ce qui s'est réellement passé. Vous obtenez les choses ont commencé en appelant deferred.resolve
, en spécifiant la valeur initiale que vous souhaitez passer à la première étape dans la chaîne:
deferred.resolve('initial value');
Et puis...toujours rien ne se passe. Pour s'assurer que les changements de modèle sont bien suivies, Angulaire n'a pas fait appel de la première étape de la chaîne jusqu'à la prochaine fois $apply
s'appelle:
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
Alors, erreur de manipulation? Jusqu'à présent, nous avons seulement indiqué un gestionnaire de succès à chaque étape de la chaîne. then
accepte également un gestionnaire d'erreur, comme un deuxième argument optionnel. En voici un autre, plus l'exemple d'une promesse de la chaîne, cette fois avec la 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 l'occasion de détourner le trafic vers la prochaine erreur de gestionnaire au lieu de la prochaine succès gestionnaire. Dans la plupart des cas, vous pouvez avoir un seul gestionnaire d'erreur à la fin de la chaîne, mais vous pouvez aussi avoir l'intermédiaire des gestionnaires d'erreur que la tentative de récupération.
Pour revenir rapidement à vos exemples et vos questions), je vais juste dire qu'elles représentent deux manières différentes pour s'adapter Facebook callback de l'API orientée Angulaire de la voie de l'observation des changements de modèle. Le premier exemple encapsule l'appel d'API en une promesse, qui peut être ajouté à un champ d'application et est entendu par Angulaire du système de template. Le second prend le plus de force brute approche de définition de la fonction de rappel directement le résultat sur le champ, puis en appelant $scope.$digest()
faire Angulaire au courant du changement à partir d'une source externe.
Les deux exemples qui ne sont pas directement comparables, car la première est manquant, l'étape de connexion. Cependant, il est généralement souhaitable d'encapsuler les interactions avec les Api externe de ce genre dans des services distincts, et d'envoyer les résultats aux contrôleurs que des promesses. De cette façon, vous pouvez garder vos contrôleurs séparés de préoccupations externes, et de les tester plus facilement avec des simulations de services.