AngularJS: ¿Dónde usar las promesas?

Vi algunos ejemplos de servicios de inicio de sesión de Facebook que estaban usando promesas para acceder a la API de FB Graph.

Ejemplo n. ° 1 :

this.api = function(item) { var deferred = $q.defer(); if (item) { facebook.FB.api('/' + item, function (result) { $rootScope.$apply(function () { if (angular.isUndefined(result.error)) { deferred.resolve(result); } else { deferred.reject(result.error); } }); }); } return deferred.promise; } 

Y los servicios que usaron "$scope.$digest() // Manual scope evaluation" cuando obtuvieron la respuesta

Ejemplo # 2 :

 angular.module('HomePageModule', []).factory('facebookConnect', function() { return new function() { this.askFacebookForAuthentication = function(fail, success) { FB.login(function(response) { if (response.authResponse) { FB.api('/me', success); } else { fail('User cancelled login or did not fully authorize.'); } }); } } }); function ConnectCtrl(facebookConnect, $scope, $resource) { $scope.user = {} $scope.error = null; $scope.registerWithFacebook = function() { facebookConnect.askFacebookForAuthentication( function(reason) { // fail $scope.error = reason; }, function(user) { // success $scope.user = user $scope.$digest() // Manual scope evaluation }); } } 

JSFiddle

Las preguntas son:

  • ¿Cuál es la diferencia en los ejemplos anteriores?
  • ¿Cuáles son los motivos y casos para usar el servicio $ q ?
  • ¿Y cómo funciona ?

    Esta no será una respuesta completa a su pregunta, pero espero que esto lo ayude a usted y a los demás cuando intente leer la documentación del servicio $q . Me tomó un tiempo entenderlo.

    Dejemos a un lado AngularJS por un momento y solo consideremos las llamadas a la API de Facebook. Ambas llamadas API utilizan un mecanismo de callback 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 asíncronas 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 hace 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 fue exitoso realiza la solicitud a Graph API.

    Incluso en este caso, que solo está encadenando juntas dos operaciones, las cosas empiezan a complicarse. El método askFacebookForAuthentication acepta una callback por fallas y éxito, pero ¿qué sucede cuando FB.login tiene éxito pero falla FB.api ? Este método siempre invoca la callback FB.api independientemente del resultado del método FB.api .

    Ahora imagine que está tratando de codificar una secuencia robusta de tres o más operaciones asíncronas, de forma que maneje correctamente los errores en cada paso y sea legible para cualquier persona o incluso para usted después de algunas semanas. Posible, pero es muy fácil seguir anidando esas devoluciones de llamadas y perder el seguimiento de los errores en el camino.

    Ahora, dejemos a un lado la API de Facebook por un momento y solo tengamos en cuenta la API de Promesas Angulares, implementada por el servicio $q . El patrón implementado por este servicio es un bash de convertir la progtwigció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 bloque familiar try/catch .

    Considera 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 imagina que firstFn y secondFn tardan mucho en completarse, por lo que queremos procesar esta secuencia de forma asincrónica. Primero creamos un nuevo objeto deferred , que representa una cadena de operaciones:

      var deferred = $q.defer(); var promise = deferred.promise; 

    La propiedad de la promise representa el resultado final de la cadena. Si registra una promesa inmediatamente después de crearla, verá que solo es un objeto vacío ( {} ). Nada que ver todavía, muévase adelante.

    Hasta ahora, nuestra promesa solo representa el punto de partida en la cadena. Ahora agreguemos nuestras dos operaciones:

      promise = promise.then(firstFn).then(secondFn); 

    El método then 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 establecido nuestra cadena de funciones, pero en realidad nada ha sucedido. Para empezar, llame a deferred.resolve , especificando el valor inicial que desea pasar al primer paso real de la cadena:

      deferred.resolve('initial value'); 

    Y luego … todavía no pasa nada. Para garantizar que los cambios en el modelo se cumplan correctamente, Angular no llama al primer paso de la cadena hasta que se llame a la próxima vez $apply :

      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 extenso de una cadena de promesas, esta vez con el 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 solo manejador de errores al final de la cadena, pero también puede tener manejadores de errores intermedios que intenten la recuperación.

    Para regresar rápidamente a sus ejemplos (y sus preguntas), solo diré que representan dos formas diferentes de adaptar la API orientada a la callbacks de Facebook a la forma en que Angular observa los cambios del modelo. El primer ejemplo envuelve la llamada API en una promesa, que se puede agregar a un scope y se entiende por el sistema de plantillas de Angular. El segundo toma el enfoque de fuerza bruta de establecer el resultado de callback directamente en el scope, y luego llamar a $scope.$digest() para que Angular tenga conocimiento 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 esta forma, puede mantener sus controladores separados de las preocupaciones externas y probarlos más fácilmente con servicios simulados.

    Esperaba una respuesta compleja que cubriera ambos: por qué se usan en general y cómo usarlo en Angular

    Este es el objective de las promesas angulares MVP (promesa viable mínima) : http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview

    Fuente:

    (para aquellos demasiado perezosos para hacer clic en los enlaces)

    index.html

           

    Messages

    • {{ message }}

    app.js

     angular.module('myModule', []) .factory('HelloWorld', function($q, $timeout) { var getMessages = function() { var deferred = $q.defer(); $timeout(function() { deferred.resolve(['Hello', 'world']); }, 2000); return deferred.promise; }; return { getMessages: getMessages }; }) .controller('HelloCtrl', function($scope, HelloWorld) { $scope.messages = HelloWorld.getMessages(); }); 

    (Sé que no resuelve tu ejemplo específico de Facebook, pero creo que los siguientes fragmentos son útiles)

    Vía: http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/


    Actualización 28 de febrero de 2014: a partir de 1.2.0, las plantillas ya no resuelven las promesas. http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

    (El ejemplo de plunker usa 1.1.5)

    A diferido representa el resultado de una operación asincrónica. Expone una interfaz que se puede utilizar para señalar el estado y el resultado de la operación que representa. También proporciona una forma de obtener la instancia de promesa asociada.

    Una promesa proporciona una interfaz para interactuar con su relación diferida, y por lo tanto, permite a las partes interesadas obtener acceso al estado y el resultado de la operación diferida.

    Al crear un diferido, su estado está pendiente y no tiene ningún resultado. Cuando resolvemos () o rechazamos () el diferido, cambia su estado a resuelto o rechazado. Aún así, podemos obtener la promesa asociada inmediatamente después de crear un diferido e incluso asignar interacciones con su resultado futuro. Esas interacciones ocurrirán solo después de que el diferido sea rechazado o resuelto.

    utilizar la promesa dentro de un controlador y asegúrese de que los datos estén disponibles o no

      var app = angular.module("app",[]); app.controller("test",function($scope,$q){ var deferred = $q.defer(); deferred.resolve("Hi"); deferred.promise.then(function(data){ console.log(data); }) }); angular.bootstrap(document,["app"]); 
     < !DOCTYPE html>      

    Hello Angular