AngularJS: dove usare le promesse?

Ho visto alcuni esempi di servizi di accesso a Facebook che utilizzavano promesse per accedere a FB Graph API.

Esempio 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 i servizi che hanno utilizzato "$scope.$digest() // Manual scope evaluation" quando hanno ottenuto la risposta

Esempio 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

Le domande sono:

  • Qual è la differenza negli esempi sopra?
  • Quali sono i motivi e i casi per utilizzare il servizio $ q ?
  • E come funziona ?

Questa non sarà una risposta completa alla tua domanda, ma spero che questo possa aiutare te e gli altri quando tenti di leggere la documentazione sul servizio $q . Mi ci è voluto un po ‘per capirlo.

Mettiamo da parte AngularJS per un momento e consideriamo solo le chiamate API di Facebook. Entrambe le chiamate API utilizzano un meccanismo di callback per notificare il chiamante quando è disponibile la risposta di Facebook:

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

Questo è un modello standard per la gestione di operazioni asincrone in JavaScript e in altri linguaggi.

Un grosso problema con questo modello si verifica quando è necessario eseguire una sequenza di operazioni asincrone, in cui ogni operazione successiva dipende dal risultato dell’operazione precedente. Questo è ciò che sta facendo questo codice:

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

Prima tenta di accedere e solo dopo aver verificato che l’accesso ha avuto esito positivo effettua la richiesta all’API Graph.

Anche in questo caso, che concatena solo due operazioni, le cose cominciano a diventare disordinate. Il metodo askFacebookForAuthentication accetta una callback per errori e successi, ma cosa succede quando FB.login riesce ma FB.api fallisce? Questo metodo richiama sempre la callback di success indipendentemente dal risultato del metodo FB.api .

Ora immagina di provare a codificare una sequenza robusta di tre o più operazioni asincrone, in un modo che gestisca correttamente gli errori in ogni fase e che sia leggibile da chiunque o anche da te dopo qualche settimana. Possibile, ma è molto facile continuare a nidificare quelle richiamate e perdere la traccia degli errori lungo la strada.

Ora, mettiamo da parte l’API di Facebook per un momento e consideriamo l’API di Angular Promises, implementata dal servizio $q . Lo schema implementato da questo servizio è un tentativo di trasformare la programmazione asincrona in qualcosa che assomiglia a una serie lineare di semplici affermazioni, con la possibilità di “lanciare” un errore in qualsiasi fase del processo e gestirlo alla fine, semanticamente simile al blocco familiare try/catch .

Considera questo esempio forzato. Diciamo che abbiamo due funzioni, in cui la seconda funzione consuma il risultato della prima:

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

Ora immagina che firstFn e secondFn abbiano bisogno di molto tempo per essere completati, quindi vogliamo elaborare questa sequenza in modo asincrono. Per prima cosa creiamo un nuovo object deferred , che rappresenta una catena di operazioni:

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

La proprietà promise rappresenta il risultato finale della catena. Se registri una promise subito dopo averla creata, vedrai che si tratta solo di un object vuoto ( {} ). Niente da vedere ancora, vai avanti.

Finora la nostra promise rappresenta solo il punto di partenza della catena. Ora aggiungiamo le nostre due operazioni:

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

Il metodo then aggiunge un passaggio alla catena e quindi restituisce una nuova promise che rappresenta il risultato finale della catena estesa. Puoi aggiungere tutti i passaggi che vuoi.

Finora, abbiamo impostato la nostra catena di funzioni, ma nulla è realmente accaduto. Puoi iniziare le cose chiamando deferred.resolve , specificando il valore iniziale che vuoi passare al primo passo effettivo della catena:

  deferred.resolve('initial value'); 

E poi … ancora non succede nulla. Per garantire che le modifiche del modello siano correttamente osservate, Angular non chiama in realtà il primo passo della catena fino alla successiva chiamata $apply :

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

Quindi per quanto riguarda la gestione degli errori? Finora abbiamo specificato solo un gestore di successo in ogni fase della catena. accetta inoltre un gestore di errori come secondo argomento opzionale. Ecco un altro esempio più lungo di una catena di promesse, questa volta con la gestione degli errori:

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

Come puoi vedere in questo esempio, ogni gestore della catena ha l’opportunità di deviare il traffico al successivo gestore di errori invece del successivo gestore di successo . Nella maggior parte dei casi è ansible avere un singolo gestore di errori alla fine della catena, ma è anche ansible avere gestori di errori intermedi che tentano il ripristino.

Per tornare rapidamente ai tuoi esempi (e alle tue domande), dirò semplicemente che rappresentano due modi diversi per adattare l’API di callback di Facebook al modo in cui Angular osserva i cambiamenti del modello. Il primo esempio racchiude la chiamata API in una promise, che può essere aggiunta a un ambito e viene compresa dal sistema di template di Angular. Il secondo richiede l’approccio più brute-force per impostare il risultato del callback direttamente sull’oscilloscopio e quindi chiamando $scope.$digest() per rendere Angular consapevole della modifica da un’origine esterna.

I due esempi non sono direttamente confrontabili, perché il primo manca il passo di accesso. Tuttavia, è generalmente consigliabile incapsulare interazioni con API esterne come questa in servizi separati e fornire i risultati ai controllori come promesse. In questo modo puoi tenere i tuoi controllori separati da preoccupazioni esterne e testarli più facilmente con i servizi di simulazione.

Mi aspettavo una risposta complessa che riguardasse entrambi: perché sono usati in generale e come usarli in Angular

Questo è il plunk per le promesse angolari MVP (promise minima praticabile) : http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview

Fonte:

(per quelli troppo pigri per fare clic sui collegamenti)

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

(So ​​che non risolve il tuo specifico esempio su Facebook ma trovo che i seguenti frammenti siano utili)

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


Aggiornamento 28 febbraio 2014: A partire dalla versione 1.2.0, le promesse non vengono più risolte dai modelli. http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

(esempio plunker usa 1.1.5.)

Un differimento rappresenta il risultato di un’operazione asincrona. Espone un’interfaccia che può essere utilizzata per segnalare lo stato e il risultato dell’operazione che rappresenta. Fornisce anche un modo per ottenere l’istanza di promise associata.

Una promise fornisce un’interfaccia per interagire con i relativi differimenti e, quindi, consente alle parti interessate di accedere allo stato e al risultato dell’operazione differita.

Quando si crea un differimento, lo stato è in sospeso e non ha alcun risultato. Quando risolviamo () o rifiutiamo () il differito, esso cambia lo stato in modo risolvibile o rifiutato. Tuttavia, possiamo ottenere la promise associata immediatamente dopo aver creato un rinvio e persino assegnare le interazioni con il suo risultato futuro. Tali interazioni avverranno solo dopo che il differito è stato respinto o risolto.

utilizzare la promise all’interno di un controller e assicurarsi che i dati siano disponibili o meno

  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"]); 
       

Hello Angular