Promise API - combining results of 2 asynchronous call
Solution 1
As @Matt said, you need to use $q.all
, but the usage isn't quite right. AngularJS doesn't support .done
and .fail
and they don't work quite like that anyway in that there's no such thing as a promise for multiple values, instead you just have a promise for an array.
If you were writing this using the full Q we would write:
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 this case, .spread
acts like .then
except that it spreads the results of an array for a promise out over the arguments of its onFulfilled
function. To change this to use the promise methods from AngularJS we just need to make do without .spread
. This leads to the following solution:
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
};
The beauty of this is that we are freed from handling all the nitty grity of error propagation and storing the partial results because .then
acts as a filter. If you leave out the error handler, it automatically propagates any errors. This means that if either of the input promises are rejected, the result will be rejected. If both promises are fulfilled successfully, res is the array of those resolution values.
Solution 2
I have something to add to @ForbesLindesay answer.
In our case, we wanted partial results: if a request failed (eg. server has an hiccup, we request something deleted by somebody else, etc.), we still want to collect the valid responses, and to report the errors.
I found out that we need to handle success and failure on each promise, returning a value that will be collected by $q.all
.
Here is our code, simplified and made generic ('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);
});
Note that we use Lodash (_.map, _.flatten, _.reject, _.isUndefined) but I think the usage is quite clear (that's the nice point of this library!).
Related videos on Youtube
bsr
Updated on September 15, 2022Comments
-
bsr over 1 year
With promise API, how to send two asynchronous request in parallel, and resolve the combined result as the response.
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; };
now, when I call get() like
get(123).then(function(d)){ // d= {res1: res1, res2: res2} }, ...
I need to get the combined result as indicated. How to do this with Angular promise API?
-
Bergi over 8 yearsYou seems to have some string literal syntax errors in there
-
PhiLho over 8 yearsFixed, thanks for the heads up. That's what I get to hand-edit code without syntax highlighting nor hinting. Too spoiled by Brackets... :-)
-
Bergi over 8 yearsThanks :-) Now that the code is readable, I'm a bit concerned about the use of
scope.errorList
. It might work in this particular case, and at the end of the chain, but it should not be used in a generic method imo. Ratherreturn
dedicated values from the error handler (instead ofundefined
s) that you can_.filter
on in the final handler. -
PhiLho over 8 yearsYes, of course. I initially thought about just returning an error object, then separate objects from arrays in the outcome (or distinguish objects by a special property on the errors, for example). I used the method above because if was OK and simpler in our case, but avoiding semi-global state is of course cleaner.