2013-04-30 13 views
17

Con API promessa, come inviare due richieste asincrone in parallelo e risolvere il risultato combinato come risposta.Promise: combinazione di risultati di 2 chiamate asincrone

var get = function(id){ 
      var res1, res2; 
      var deferred = $q.defer(); 
      Db.get(id, "abc") 
       .then(function (d) { 
        //deferred.resolve(d)); 
        res1 = d; 
       }, function (e) { 
        //error 
       }); 

      Db.get(id, "def") 
       .then(function (d) { 
        //deferred.resolve(d)); 
        res2 = d; 
       }, function (e) { 
        //error 
       }); 

      //?????? how to return {res1:res1 , res2: res2} 

      return deferred.promise; 
     }; 

ora, quando chiamo get() come

get(123).then(function(d)){ 
// d= {res1: res1, res2: res2} 
}, 
... 

ho bisogno di ottenere il risultato combinato come indicato. Come fare questo con l'API di promessa di Angular?

risposta

36

Come ha detto @Matt, è necessario utilizzare $q.all, ma l'utilizzo non è corretto. AngularJS non supporta .done e .fail e non funzionano comunque in questo modo non esiste una promessa per più valori, ma solo una promessa per un array.

se si stesse scrivendo questo usando la piena Q avremmo scrivere:

var get = function (id) { 
    return Q.all([Db.get(id, "abc"), Db.get(id, "def")]) 
     .spread(function (res1, res2) { 
      return {res1: res1, res2: res2}; 
     });//the error case is handled automatically 
}; 

In questo caso, .spread agisce come .then tranne che diffonde i risultati di una matrice per una promessa nel corso degli argomenti del suo onFulfilled funzione. Per cambiare questo per usare i metodi promettenti di AngularJS, dobbiamo solo fare a meno di .spread. Questo porta alla seguente soluzione:

var get = function (id) { 
    return $q.all([Db.get(id, "abc"), Db.get(id, "def")]) 
     .then(function (res) { 
      return {res1: res[0], res2: res[1]}; 
     });//the error case is handled automatically 
}; 

La bellezza di questo è che siamo liberati dalla gestione di tutte le grity Gritty di propagazione degli errori e memorizzare i risultati parziali perché .then funge da filtro. Se si omette il gestore degli errori, si propagano automaticamente eventuali errori. Ciò significa che se una delle promesse di input viene rifiutata, il risultato verrà respinto. Se entrambe le promesse sono soddisfatte correttamente, res è la matrice di quei valori di risoluzione.

+0

Grande risposta +1. Inoltre, vi ringrazio per aver chiarito i dettagli sulla gestione degli errori, perché non ne ero sicuro. Se si dispone di un momento, si prega di avvisare su http://stackoverflow.com/questions/16311803/chaining-2-asynchronous-calls-promise-api-to-run-serially, mi sforzo che la clausola allora non blocchi – bsr

1

Ho qualcosa da aggiungere alla risposta @ForbesLindesay.

Nel nostro caso, volevamo risultati parziali: se una richiesta non è riuscita (ad esempio il server ha un inconveniente, chiediamo qualcosa eliminato da qualcun altro, ecc.), Vogliamo ancora raccogliere le risposte valide e segnalare il errori.

Ho scoperto che è necessario gestire il successo e il fallimento su ogni promessa, restituendo un valore che verrà raccolto da $q.all.

Ecco il nostro codice, semplificato e reso generica ('item' ...):

var promiseList = _.map(itemList, function(item) 
{ 
    return DataService.getISubtems(item.id) 
     .then(
      function(response) 
      { 
       var subItems = response.data; 
       $log.info('Received sub-item list;' + subItems.length + ';items received'); 
       return subItems; 
      }, 
      function(reason) 
      { 
       $log.warn('Sub-item list not received for item;' + item.name + ';' + item.id); 
       $scope.errorList.push('Sub-item list not received for item "' + item.name + '"'); 
      } 
     ); 
}); 
$q.all(promiseList) 
    .then(function(itemArray) 
    { 
     // We get an array of arrays interleaved with undefined value when an error was raised. 
     // That's because error handling doesn't return anything, ie. returns undefined. 
     // We remove these undefined values then put all operations at the same level. 
     var allOperations = _(operationArray).reject(_.isUndefined).flatten().value(); 
     if ($scope.errorList.length > 0) 
     { 
      NotificationService.warning('Items Fetching', 'Errors while getting item list:\n' + 
       $scope.errorList.join('\n')); 
     } 
     $scope._onItemListReceived(allItems); 
    }); 

Nota che usiamo Lodash (_.map, _.flatten, _.reject, _.isUndefined) ma penso che l'uso sia abbastanza chiaro (questo è il punto di forza di questa libreria!).

+1

Sembra che ci siano degli errori di sintassi stringa letterali – Bergi

+0

Risolto, grazie per l'heads up. Questo è quello che riesco a modificare a mano il codice senza evidenziazione della sintassi né suggerimento. Troppo viziato dalle staffe ...:-) – PhiLho

+1

Grazie :-) Ora che il codice è leggibile, sono un po 'preoccupato per l'uso di 'scope.errorList'. Potrebbe funzionare in questo caso particolare e alla fine della catena, ma non dovrebbe essere usato in un metodo generico imo. Piuttosto 'restituisci 'i valori dedicati dal gestore degli errori (invece di' undefined's) che puoi '_filter' on nel gestore finale. – Bergi

0

È possibile utilizzare la libreria angular-q-spread e quindi utilizzare lo stesso codice di @ ForbesLindesay primo esempio:

// The module needs $q-spread as a dependency: 
// angular.module('…', ['$q-spread']); 

var get = function (id) { 
    return $q.all([Db.get(id, "abc"), Db.get(id, "def")]) 
     .spread(function (res1, res2) { 
      return {res1: res1, res2: res2}; 
     }); 
}; 
Problemi correlati