AngularJS: Dove usare le promesse?


141

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

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

E i servizi che hanno usato "$scope.$digest() // Manual scope evaluation"quando ha ottenuto la risposta

Esempio n. 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 ?

9
sembra che dovresti leggere quali sono le promesse e perché sono usate in generale ... non sono esclusive dell'angolare e c'è molto materiale disponibile
charlietfl

1
@charlietfl, buon punto, ma mi aspettavo una risposta complessa che riguarderà entrambi: perché sono usati in generale e come usarli in angolare. Grazie per il tuo suggerimento
Maksym,

Risposte:


401

Questa non sarà una risposta completa alla tua domanda, ma spero che questo possa aiutare te e gli altri quando provi a leggere la documentazione sul $qservizio. 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 richiamata per avvisare il chiamante quando è disponibile la risposta da 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 altre lingue.

Un grosso problema con questo modello si presenta quando è necessario eseguire una sequenza di operazioni asincrone, in cui ogni operazione successiva dipende dal risultato dell'operazione precedente. Ecco cosa 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.');
      }
  });

In primo luogo tenta di accedere, quindi solo dopo aver verificato che l'accesso abbia avuto esito positivo invia la richiesta all'API Graph.

Anche in questo caso, che sta concatenando solo due operazioni, le cose iniziano a diventare confuse. Il metodo askFacebookForAuthenticationaccetta un callback per fallimento e successo, ma cosa succede quando FB.loginriesce ma FB.apifallisce? Questo metodo richiama sempre il successcallback indipendentemente dal risultato del FB.apimetodo.

Ora immagina di provare a codificare una sequenza solida di tre o più operazioni asincrone, in un modo che gestisca correttamente gli errori in ogni fase e sarà leggibile da chiunque o anche da te dopo poche settimane. Possibile, ma è molto semplice continuare a annidare quei callback e perdere la traccia degli errori lungo la strada.

Ora, mettiamo da parte l'API di Facebook per un momento e consideriamo solo l'API Angular Promises, come implementata dal $qservizio. Il modello implementato da questo servizio è un tentativo di trasformare la programmazione asincrona in qualcosa che assomiglia a una serie lineare di semplici istruzioni, con la possibilità di "gettare" un errore in qualsiasi fase del percorso e gestirlo alla fine, semanticamente simile al try/catchblocco familiare .

Considera questo esempio inventato. Supponiamo di avere 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 impieghino molto tempo per essere completati, quindi vogliamo elaborare questa sequenza in modo asincrono. Innanzitutto creiamo un nuovo deferredoggetto, che rappresenta una catena di operazioni:

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

La promiseproprietà rappresenta l'eventuale risultato della catena. Se registri una promessa subito dopo averla creata, vedrai che è solo un oggetto vuoto ({} ). Niente da vedere ancora, muoviti subito.

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

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

Il thenmetodo aggiunge un passaggio alla catena e quindi restituisce una nuova promessa che rappresenta il risultato finale della catena estesa. Puoi aggiungere tutti i passaggi che desideri.

Finora abbiamo creato la nostra catena di funzioni, ma in realtà non è successo nulla. Puoi iniziare le cose chiamando deferred.resolve, specificando il valore iniziale che vuoi passare al primo passo effettivo nella catena:

 deferred.resolve('initial value');

E poi ... non succede ancora niente. Per garantire che le modifiche al modello vengano osservate correttamente, Angular non chiama in realtà il primo passo della catena fino alla successiva $applychiamata:

 deferred.resolve('initial value');
 $rootScope.$apply();

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

E la gestione degli errori? Finora abbiamo specificato solo un gestore di successo in ogni fase della catena. thenaccetta anche un gestore di errori come secondo argomento facoltativo. 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, ciascun gestore della catena ha l'opportunità di deviare il traffico al gestore degli errori successivo anziché al gestore dei successi successivo . Nella maggior parte dei casi è possibile avere un singolo gestore errori alla fine della catena, ma è anche possibile avere gestori errori intermedi che tentano il ripristino.

Per tornare rapidamente ai tuoi esempi (e alle tue domande), dirò solo che rappresentano due modi diversi per adattare l'API orientata alla callback di Facebook al modo di Angular di osservare i cambiamenti del modello. Il primo esempio racchiude la chiamata API in una promessa, che può essere aggiunta a un ambito ed è compresa dal sistema di modelli di Angular. Il secondo utilizza l'approccio più bruto di impostare il risultato della richiamata direttamente sull'ambito e quindi chiamare $scope.$digest()per rendere Angular consapevole del cambiamento da una fonte esterna.

I due esempi non sono direttamente confrontabili, poiché al primo manca il passaggio di accesso. Tuttavia, è generalmente desiderabile incapsulare interazioni con API esterne come questa in servizi separati e fornire i risultati ai controller come promesso. In questo modo è possibile mantenere i controller separati da preoccupazioni esterne e testarli più facilmente con servizi simulati.


5
Penso che sia un'ottima risposta! La cosa principale, per me, stava descrivendo il caso generale quando la promessa è veramente reale. Onestamente speravo in un altro esempio reale (come su Facebook), ma credo che funzioni anche questo. Grazie molto!
Maksym,

2
Un'alternativa al concatenamento di più thenmetodi è l'uso $q.all. Un breve tutorial su questo può essere trovato qui .
Bogdan,

2
$q.allè appropriato se è necessario attendere il completamento di più operazioni asincrone indipendenti . Non sostituisce il concatenamento se ogni operazione dipende dal risultato dell'operazione precedente.
karlgold

1
il concatenamento di allora è spiegato succintamente qui. Mi ha aiutato a capire e ad usarlo al massimo delle sue potenzialità. Grazie
Tushar Joshi

1
Ottima risposta @karlgold! Ho una domanda. Se, nell'ultimo snippet di codice, cambi la return 'firstResult'parte in return $q.resolve('firstResult'), quale sarà la differenza?
tecnophyle

9

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

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

Fonte:

(per quelli troppo pigri per fare clic sui collegamenti)

index.html

  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-app="myModule" ng-controller="HelloCtrl">
    <h1>Messages</h1>
    <ul>
      <li ng-repeat="message in messages">{{ message }}</li>
    </ul>
  </body>

</html>

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 esempio specifico di Facebook ma trovo utili i seguenti frammenti)

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


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

(esempio di plunker utilizza 1.1.5.)


afaik amiamo così perché siamo pigri
mkb

questo mi ha aiutato a capire $ q, differito e incatenato. quindi chiamate di funzione, quindi grazie.
aliopi,

1

Un differito 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 inoltre un modo per ottenere l'istanza di promessa associata.

Una promessa fornisce un'interfaccia per l'interazione con i relativi rinviati e, quindi, consente alle parti interessate di avere accesso allo stato e al risultato dell'operazione differita.

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


1

usa promessa all'interno di un controller e assicurati 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"]);
<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
  </head>

  <body>
    <h1>Hello Angular</h1>
    <div ng-controller="test">
    </div>
  </body>

</html>

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.