Promise API - combining results of 2 asynchronous call

14,013

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!).

Share:
14,013

Related videos on Youtube

bsr
Author by

bsr

Updated on September 15, 2022

Comments

  • bsr
    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
    Bergi over 8 years
    You seems to have some string literal syntax errors in there
  • PhiLho
    PhiLho over 8 years
    Fixed, thanks for the heads up. That's what I get to hand-edit code without syntax highlighting nor hinting. Too spoiled by Brackets... :-)
  • Bergi
    Bergi over 8 years
    Thanks :-) 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. Rather return dedicated values from the error handler (instead of undefineds) that you can _.filter on in the final handler.
  • PhiLho
    PhiLho over 8 years
    Yes, 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.