Esta no será una respuesta completa a su pregunta, pero espero que esto lo ayude a usted y a otros cuando intente leer la documentación del $q
servicio. Me tomó un tiempo entenderlo.
Dejemos de lado AngularJS por un momento y solo consideremos las llamadas API de Facebook. Ambas llamadas API utilizan un mecanismo de devolución de llamada para notificar a la persona que llama cuando la respuesta de Facebook está disponible:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
Este es un patrón estándar para manejar operaciones asincrónicas en JavaScript y otros idiomas.
Un gran problema con este patrón surge cuando necesita realizar una secuencia de operaciones asincrónicas, donde cada operación sucesiva depende del resultado de la operación anterior. Eso es lo que está haciendo este código:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
Primero intenta iniciar sesión, y solo después de verificar que el inicio de sesión se realizó correctamente, realiza la solicitud a la API Graph.
Incluso en este caso, que solo está encadenando dos operaciones, las cosas comienzan a complicarse. El método askFacebookForAuthentication
acepta una devolución de llamada por falla y éxito, pero ¿qué sucede cuando FB.login
tiene éxito pero FB.api
falla? Este método siempre invoca la success
devolución de llamada independientemente del resultado del FB.api
método.
Ahora imagine que está tratando de codificar una secuencia robusta de tres o más operaciones asincrónicas, de una manera que maneje adecuadamente los errores en cada paso y sea legible para cualquier otra persona o incluso para usted después de unas pocas semanas. Es posible, pero es muy fácil seguir anidando esas devoluciones de llamada y perder la noción de errores en el camino.
Ahora, dejemos de lado la API de Facebook por un momento y consideremos la API de Promesas Angulares, tal como la implementa el $q
servicio. El patrón implementado por este servicio es un intento de convertir la programación asincrónica en algo parecido a una serie lineal de declaraciones simples, con la capacidad de 'arrojar' un error en cualquier paso del camino y manejarlo al final, semánticamente similar al try/catch
Bloque familiar .
Considere este ejemplo artificial. Digamos que tenemos dos funciones, donde la segunda función consume el resultado de la primera:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
Ahora imagine que firstFn y secondFn tardan mucho tiempo en completarse, por lo que queremos procesar esta secuencia de forma asincrónica. Primero creamos un nuevo deferred
objeto, que representa una cadena de operaciones:
var deferred = $q.defer();
var promise = deferred.promise;
La promise
propiedad representa el resultado final de la cadena. Si registra una promesa inmediatamente después de crearla, verá que es solo un objeto vacío ( {}
). Aún no hay nada que ver, muévete.
Hasta ahora, nuestra promesa solo representa el punto de partida de la cadena. Ahora agreguemos nuestras dos operaciones:
promise = promise.then(firstFn).then(secondFn);
El then
método agrega un paso a la cadena y luego devuelve una nueva promesa que representa el resultado final de la cadena extendida. Puede agregar tantos pasos como desee.
Hasta ahora, hemos configurado nuestra cadena de funciones, pero en realidad no ha pasado nada. Empiezas las cosas llamando deferred.resolve
, especificando el valor inicial que deseas pasar al primer paso real de la cadena:
deferred.resolve('initial value');
Y entonces ... todavía no pasa nada. Para garantizar que los cambios en el modelo se observen correctamente, Angular en realidad no llama al primer paso en la cadena hasta que $apply
se llame la próxima vez :
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
Entonces, ¿qué pasa con el manejo de errores? Hasta ahora solo hemos especificado un controlador de éxito en cada paso de la cadena. then
También acepta un controlador de errores como un segundo argumento opcional. Aquí hay otro ejemplo más largo de una cadena de promesa, esta vez con manejo de errores:
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);
Como puede ver en este ejemplo, cada controlador de la cadena tiene la oportunidad de desviar el tráfico al siguiente controlador de errores en lugar del siguiente controlador de éxito . En la mayoría de los casos, puede tener un único controlador de errores al final de la cadena, pero también puede tener controladores de errores intermedios que intenten la recuperación.
Para volver rápidamente a sus ejemplos (y sus preguntas), solo diré que representan dos formas diferentes de adaptar la API orientada a la devolución de llamadas de Facebook a la forma en que Angular observa los cambios en el modelo. El primer ejemplo envuelve la llamada API en una promesa, que se puede agregar a un alcance y se entiende por el sistema de plantillas de Angular. El segundo adopta el enfoque de fuerza más bruta de establecer el resultado de devolución de llamada directamente en el alcance y luego llamar $scope.$digest()
para que Angular sea consciente del cambio desde una fuente externa.
Los dos ejemplos no son directamente comparables, porque al primero le falta el paso de inicio de sesión. Sin embargo, generalmente es deseable encapsular interacciones con API externas como esta en servicios separados y entregar los resultados a los controladores como promesas. De esa manera, puede mantener sus controladores separados de las preocupaciones externas y probarlos más fácilmente con servicios simulados.