3

I have an AngularJS service with such async API:

myService.asyncCall(['id0', 'id3', 'id2']).then(function (complexData) {   
  // handle complexData
}).catch(function (error) {   
  console.log(error); 
});

asyncCall incapsulates multiple calls of $http which handled by $q.all. Each of the $http requests can response with error and I want the errors to be handled by one catch handler. So how i can achieve multiple calls of the catch handler ?

6
  • 1
    $q.all gets rejected when any of the promises gets rejected. What exactly are you trying to achieve ? Commented Apr 29, 2014 at 13:54
  • @ExpertSystem, Yes, But I want to handle each possible reject from the promises array that I pass to $q.all Commented Apr 29, 2014 at 13:59
  • Sorry, I still don't understand what you want to achieve (that's why I asked for clarification). What exactly doyou mean by "handle each possible reject from the promises array". Commented Apr 29, 2014 at 14:02
  • Ok, forgot about $q.all. I want my asyncCall 1. to get array of promises, 2. has one resolve handler for each resolved promise from the array (then block from the example) 3. has one reject handler that will be called for each rejected promise. (catch block from the example). I hope now it is clear. Commented Apr 29, 2014 at 14:16
  • I still have questions :) 1. Do you want the requests to happen in parallel, sequentially, or whatever ? 2. What do you want to happen when any of the requests fails ? Continue with the rest or abort all ? 3. I understand that you want to have two functions: 1 for handling the success of any request and one for handing the error of any request (so if 3 requests succeed, your success handler should be called 3 times). Is that correct or am I still missing the point ? Commented Apr 29, 2014 at 14:23

2 Answers 2

5

If I understood correctly (finally), this is what you are trying to achieve:

  • Input:
    1. a list of promises
    2. a callback to be called when all promises have been resolved
    3. a callback to be called each time any of the promises gets rejected

  • Behaviour:
    1. You want parallel execution (not sequential).
    2. You want all promises to be either resolved or rejected (i.e. even if one promise gets rejected, the rest should continue as usual).
    3. You want the "final" callback to be called exactly once (receiving an array of results - regardless if the respective deferred was resolved or rejected).

$q.all should be "augmented" to suit your needs, because by default:

  1. It gets immediatelly rejected as soon as any of the promises in the list gets rejected.
  2. In case of rejection it returns only the rejection reason and not a list of results.

Here is a possible implementation:

function asyncCall(listOfPromises, onErrorCallback, finalCallback) {

  listOfPromises  = listOfPromises  || [];
  onErrorCallback = onErrorCallback || angular.noop;
  finalCallback   = finalCallback   || angular.noop;

  // Create a new list of promises that can "recover" from rejection
  var newListOfPromises = listOfPromises.map(function (promise) {
    return promise.catch(function (reason) {

      // First call the `onErrroCallback`
      onErrorCallback(reason);

      // Change the returned value to indicate that it was rejected
      // Based on the type of `reason` you might need to change this
      // (e.g. if `reason` is an object, add a `rejected` property)
      return 'rejected:' + reason;

    });
  });

  // Finally, we create a "collective" promise that calls `finalCallback` when resolved.
  // Thanks to our modifications, it will never get rejected !
  $q.all(newListOfPromises).then(finalCallback);
}

See, also, this short demo.

Sign up to request clarification or add additional context in comments.

4 Comments

Yes, Almost what I am looking for but with the only exception that I need only resolved results without rejected ones in success handler. Any ideas how can change your all modification?
It is generally not very usefull to remove some results, because then there will be no way to know which result belongs to which promise. In any case, you could filter the results before passing them to finalCallback. E.g. replace .then(finalCallback) with .then(function(results){finalCallback(results.filter(function(res){return res.indexOf('rejected:') !== 0;}));}).
What if I need the finalCallback to be executed only if there are no rejected promises???
@ppollono: Add a flag that is set to true before the onErrorCallback() is called and check it before calling finalCallback. It really depends on your exact requirements.
1

One way, would be to attach a .catch handler indidually in your service:

function asyncCall(urls){
    var calls = urls.map(makeSomeCall).
                map(function(prom){ return prom.catch(e){ /* recover here */});
    return $q.all(calls);
};

Another way would be to implement a settle method that is like $q.all but keeps track of all results. Stealing from my answer here:

function settle(promises){
     var d = $q.defer();
     var counter = 0;
     var results = Array(promises.length);
     promises.forEach(function(p,i){ 
         p.then(function(v){ // add as fulfilled
              results[i] = {state:"fulfilled", promise : p, value: v};
         }).catch(function(r){ // add as rejected
              results[i] = {state:"rejected", promise : p, reason: r};
         }).finally(function(){  // when any promises resolved or failed
             counter++; // notify the counter
             if (counter === promises.length) {
                d.resolve(results); // resolve the deferred.
             }
         });
     });
})

You can handle multiple promises as such:

var promiseArr = ["id0","id3","id2"].map(makePromise);

settle(promiseArr).then(function(results){
    var failed = results.filter(function(r){ return r.state === "rejected"; });
    var failedValues = failed.map(function(i){ return i.value; });
    var done = results.filter(function(r){ return r.state === "fulfilled"; });
     var doneValues = done.map(function(i){ return i.value; }); 
});

Which gives you access to all results of the promise, regardless if it failed or not, and let you recover with more granularity.

Your service should handle that aggregation since it's the one returning a promise on everything. One example would be to do:

if(failed.length > 0){
     throw new Error("The following failed..."); // some info about all failed
}

4 Comments

What if I want to show error notification each time when $http answers with error ? That is why i want multiple calls of catch
Look at the first approach I suggested. That should do it.
Thought about such solution, but in this case then handler will not be called if at least one of promises will be rejected (because of all behaviour). In other words in need kind of all that will call then for all resolved promises and catch for each rejected promise.
How would it look in synchronous code? @e.gluhotorenko

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.