AngularJS: Onde usar promises?

Eu vi alguns exemplos de serviços de login do Facebook que estavam usando promises para acessar a API do FB Graph.

Exemplo 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; } 

E serviços que usaram "$scope.$digest() // Manual scope evaluation" quando obtiveram a resposta

Exemplo 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

As perguntas são:

  • Qual é a diferença nos exemplos acima?
  • Quais são as razões e casos para usar o serviço $ q ?
  • E como isso funciona ?

Esta não será uma resposta completa à sua pergunta, mas esperamos que isso ajude você e outras pessoas quando você tentar ler a documentação do serviço $q . Demorei um pouco para entender.

Vamos deixar de lado o AngularJS por um momento e considerar apenas as chamadas da API do Facebook. Ambas as chamadas de API usam um mecanismo de retorno de chamada para notificar o chamador quando a resposta do Facebook está disponível:

  facebook.FB.api('/' + item, function (result) { if (result.error) { // handle error } else { // handle success } }); // program continues while request is pending ... 

Este é um padrão padrão para lidar com operações assíncronas em JavaScript e outros idiomas.

Um grande problema com esse padrão surge quando você precisa executar uma sequência de operações assíncronas, em que cada operação sucessiva depende do resultado da operação anterior. É o que esse código está fazendo:

  FB.login(function(response) { if (response.authResponse) { FB.api('/me', success); } else { fail('User cancelled login or did not fully authorize.'); } }); 

Primeiro, ele tenta efetuar login e, depois de verificar se o login foi bem-sucedido, ele faz a solicitação para a API do Graph.

Mesmo neste caso, que é apenas encadear duas operações, as coisas começam a ficar confusas. O método askFacebookForAuthentication aceita um retorno de chamada para falha e sucesso, mas o que acontece quando o FB.login é FB.login sucedido, mas o FB.api falha? Esse método sempre invoca o retorno de chamada de success independentemente do resultado do método FB.api .

Agora imagine que você está tentando codificar uma sequência robusta de três ou mais operações assíncronas, de uma maneira que lide corretamente com erros em cada etapa e seja legível para qualquer outra pessoa ou mesmo para você depois de algumas semanas. Possível, mas é muito fácil manter apenas os callbacks e perder a noção dos erros ao longo do caminho.

Agora, vamos deixar de lado a API do Facebook por um momento e considerar apenas a API Angular Promises, implementada pelo serviço $q . O padrão implementado por este serviço é uma tentativa de transformar a programação assíncrona de volta em algo parecido com uma série linear de instruções simples, com a capacidade de ‘lançar’ um erro em qualquer passo do caminho e lidar com ele no final, semanticamente similar ao familiar try/catch bloco de try/catch .

Considere este exemplo inventado. Digamos que temos duas funções, onde a segunda function consome o resultado da primeira:

  var firstFn = function(param) { // do something with param return 'firstResult'; }; var secondFn = function(param) { // do something with param return 'secondResult'; }; secondFn(firstFn()); 

Agora imagine que firstFn e secondFn demorem muito tempo para serem concluídos, portanto, queremos processar essa sequência de forma assíncrona. Primeiro criamos um novo object deferred , que representa uma cadeia de operações:

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

A propriedade promise representa o resultado final da cadeia. Se você registrar uma promise imediatamente após criá-la, verá que é apenas um object vazio ( {} ). Nada para ver ainda, siga em frente.

Até agora, nossa promise representa apenas o ponto de partida da cadeia. Agora vamos adicionar nossas duas operações:

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

O método then adiciona uma etapa à cadeia e retorna uma nova promise que representa o resultado final da cadeia estendida. Você pode adicionar quantas etapas desejar.

Até agora, montamos nossa cadeia de funções, mas nada aconteceu. Você começa as coisas chamando deferred.resolve , especificando o valor inicial que você deseja passar para a primeira etapa real da cadeia:

  deferred.resolve('initial value'); 

E então … ainda nada acontece. Para garantir que as alterações do modelo sejam devidamente observadas, o Angular na verdade não chama o primeiro passo na cadeia até a próxima vez que $apply for chamado:

  deferred.resolve('initial value'); $rootScope.$apply(); // or $rootScope.$apply(function() { deferred.resolve('initial value'); }); 

Então, o que acontece com o tratamento de erros? Até agora, apenas especificamos um manipulador de sucesso em cada etapa da cadeia. then também aceita um manipulador de erros como um segundo argumento opcional. Aqui está outro exemplo mais longo de uma cadeia de promises, desta vez com tratamento de erros:

  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 você pode ver neste exemplo, cada manipulador na cadeia tem a oportunidade de desviar o tráfego para o próximo manipulador de erros, em vez do próximo manipulador de sucesso . Na maioria dos casos, você pode ter um único manipulador de erros no final da cadeia, mas também pode ter manipuladores de erro intermediários que tentam a recuperação.

Para retornar rapidamente aos seus exemplos (e suas perguntas), direi apenas que eles representam duas maneiras diferentes de adaptar a API orientada para retorno de chamada do Facebook à maneira de observar as alterações no modelo do Angular. O primeiro exemplo envolve a chamada da API em uma promise, que pode ser adicionada a um escopo e é compreendida pelo sistema de templates da Angular. O segundo usa a abordagem de força bruta para definir o resultado do retorno de chamada diretamente no escopo e, em seguida, chamar $scope.$digest() para tornar Angular ciente da alteração de uma fonte externa.

Os dois exemplos não são diretamente comparáveis, porque o primeiro está faltando a etapa de login. No entanto, geralmente é desejável encapsular interações com APIs externas como essa em serviços separados e entregar os resultados aos controladores como promises. Dessa forma, você pode manter seus controladores separados das preocupações externas e testá-los mais facilmente com serviços simulados.

Eu esperava uma resposta complexa que cobriria ambos: por que eles são usados ​​em geral e como usá-lo em Angular

Este é o plunk para promises angulares MVP (promise viável mínima) : http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview

Fonte:

(para aqueles com preguiça de clicar nos links)

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(); }); 

(Eu sei que isso não resolve o seu exemplo específico no Facebook, mas eu acho os seguintes trechos úteis)

Via: http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/


Atualização de 28 de fevereiro de 2014: A partir de 1.2.0, as promises não são mais resolvidas pelos modelos. http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

(Exemplo de plunker usa 1.1.5.)

Um diferido representa o resultado de uma operação assincrônica. Ele expõe uma interface que pode ser usada para sinalizar o estado e o resultado da operação que ele representa. Ele também fornece uma maneira de obter a instância de promise associada.

Uma promise fornece uma interface para interagir com ela relacionada diferida e, assim, permite que as partes interessadas tenham access ao estado e ao resultado da operação adiada.

Ao criar um diferido, o estado está pendente e não tem nenhum resultado. Quando resolvemos () ou rejeitamos () o diferido, ele muda seu estado para resolvido ou rejeitado. Ainda assim, podemos obter a promise associada imediatamente depois de criar uma interação adiada e até designar seu resultado futuro. Essas interações ocorrerão somente após o diferido rejeitado ou resolvido.

use a promise dentro de um controlador e verifique se os dados estão disponíveis ou não

  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