14

Am I misunderstanding Promise.all? I have X promises in an array and i'm trying to aggregate the success/failure ratio of the array.

Here is what I think I know:

Promise.all takes an array of promises.

If all of the promises succeed then the .then callback is ran.

If one of the promises fail then the .catch callback is called and the argument passed in is the value of the single raised error.

There is no callback fired which is the result of all the promises if some succeed and some fail. I.e. it can't give you an array like (pseudo code) [success, fail, success, success] - like one would expect and one can find in many JS libraries (ajax, ember, etc).

It's like the .then is more like a .success, not a function that always runs after all the promises are fulfilled regardless of whether some succeeded or some failed. Why doesn't have a .when .finally .runThisShizNoMatterWhat?? Or am I missing something (very probable)?

Taysky
  • 4,331
  • 2
  • 20
  • 28
  • 2
    Your question is valid, but asking for help and simultaneously attacking the people who designed the language you use seems counterproductive. – loganfsmyth Oct 06 '15 at 20:18
  • Success is exactly what `.then(x)` is. You're thinking of `.then(x,y)`. I for one applaud ES6 for sticking to core features and not going down the road of adding everyone's favorite helpers. – jib Oct 07 '15 at 14:36

6 Answers6

14

This is related to Bluebird Promise.all - multiple promises completed aggregating success and rejections, but that's Bluebird-specific. The core of the issue is that if you want to inspect whether something succeeded or failed, then you aren't really asking for the direct result of each promise. Instead, you'd want to transform the promises before using Promise.all. There is no helper for this ES6-standard promises, but it is trivial to implement. In most libraries, this is known as Promise.settle. For example

var someThings = [...]; // some list of promises that may succeed or fail
settle(someThings).then(results => {
  results.forEach(result => {
    if (result.state === 'fullfilled'){
      console.log('succeeded', result.value);
    } else {
      console.log('failed', result.value);
    }
  });
});


function settle(arr){
  return Promise.all(arr.map(promise => {
    return promise.then(
      value => ({state: 'fullfilled', value}),
      value => ({state: 'rejected', value})
    );
  }));
}
Community
  • 1
  • 1
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
4

If you're fine telling errors apart from your values, then doing what you want is as simple as:

Promise.all(array.map(promise => promise.catch(error => error)))

var log = msg => div.innerHTML += "<p>" + msg + "</p>";

var a = () => Promise.resolve(1);
var b = () => Promise.reject("error");
var c = () => Promise.resolve(3);

Promise.all([a(), b(), c()].map(p => p.catch(e => e))).then(r => log(r));
<div id="div"></div>
jib
  • 40,579
  • 17
  • 100
  • 158
3

If one of the promises rejects, then the promise returned by Promise.all is rejected. So the rejection handler will be called right after one of the promises rejection. This might not be the desired behaviour if you just want to run all Promises without worrying about rejections (i.e. don't reject the promise if any promise rejects).

You can still handle each individual promise rejection so it will fulfill after the rejection.

var promiseRejected = new Promise(function(resolve, reject){
  setTimeout(function(){
    reject('I was rejected');
    }, 1000);
  });

promiseRejected = promiseRejected.then(null, function(reason){
  //I was rejected, now lets fullfill it.
  return reason;
  });

var promiseResolved = new Promise(function(resolve, reject){
  setTimeout(function(){
    resolve('All good');
    }, 1500);
  });

var time = performance.now();

Promise.all([promiseRejected, promiseResolved]).then(function(results){
  //both promises fulfilled; 1500 msecs passed
  console.log(results[0], results[1], performance.now() - time);
  });

A promise constructor that resolves when all promises have been resolved/rejected example:

Promise.when = function (arrPromises) {
    if (!Array.isArray(arrPromises)) {
        return new TypeError('Expecting an Array of Promises');
    }
    return new Promise(function (resolve, reject) {
        var len = arrPromises.length,
            values = [],
            settled = 0;

        function settle(value, index) {
            values[index] = value;
            settled++;
            if (len === settled) {
                resolve(values);
            }
        }
        if (len === 0) {
            resolve([]);
        } else {
            arrPromises.forEach(function (promise, index) {
                var handler = function (value) {
                    settle(value, index);
                };
                promise.then(handler, handler);
            });
        }
    });
}
MinusFour
  • 13,913
  • 3
  • 30
  • 39
2

Promise.all creates a new promise that can only either be resolved or rejected as a whole. Maybe you can think of it as having the every array method semantics which comes back with false when the first element doesn't match the predicate.

The then function takes up to two arguments, the second being the rejected handler. In this sense it's more than just a success, it can actually handle all cases. The catch is only a convenience method, short for .then(undefined, function(reason) { ... }).

The promise API does not have what you need I'm afraid, you would have to implement it yourself.

kraf
  • 1,269
  • 12
  • 21
1

From your question it seems that you are expecting to settle all the promises, which method promise.all doesn't guarantee, only method promise.settle guarantees to settle each promise in the array.

If you want the result of promise.all, while also settling each promise, plus have a notification of which promise resolved or rejected, then method spex.batch is exactly what you need.

Example as copied from Batch Processing:

var spex = require('spex')(Promise);

// function that returns a promise;
function getWord() {
    return Promise.resolve("World");
}

// function that returns a value;
function getExcl() {
    return '!';
}

// function that returns another function;
function nested() {
    return getExcl;
}

var values = [
    123,
    "Hello",
    getWord,
    Promise.resolve(nested)
];

spex.batch(values)
    .then(function (data) {
        console.log("DATA:", data);
    }, function (reason) {
        console.log("REASON:", reason);
    });

This outputs:

DATA: [ 123, 'Hello', 'World', '!' ]

Now let's make it fail by changing getWord to this:

function getWord() {
    return Promise.reject("World");
}

Now the output is:

REASON: [ { success: true, result: 123 },
  { success: true, result: 'Hello' },
  { success: false, result: 'World' },
  { success: true, result: '!' } ]

i.e. the entire array is settled, reporting index-bound results.

And if instead of reporting the entire reason we call getErrors():

console.log("REASON:", reason.getErrors());

then the output will be:

REASON: [ 'World' ]

This is just to simplify quick access to the list of errors that occurred.

And you can see from the method's protocol that you can pass in optional cb - callback parameter that will be telling you which of the promises resolved and which ones rejected.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
0

I agree that using Bluebird.reflect to implement settle is the best way, if you are using Bluebird. Here is another solution that may be good depending upon your use case. It worked for me in a somewhat interesting situation. This also assumes Bluebird as the promise library.

In my case, I have an array of functions ("tasks") which are all wrapped in Promise.method. These tasks may return a rejected promise or they may return or throw plain values that will become promise resolutions. I needed to execute all of these and collect all results (not just first failure).

Again, bear in mind that each item in the tasks array is a Promise.method wrapped function. (Easy to implement. See http://bluebirdjs.com/docs/api/promise.method.html)

var runTasks = function(tasks) {
     return Bluebird.reduce(tasks, function (results, task) {
          return task()
            .then(function (result) {
              results.success.push(result)
              return results
            })
            .caught(function (result) {
              results.fail.push(result)
              return results
            })
     }, { success: [], fail: [] })
}

You would then call it like this, getting back an object that has an array of fulfilled values and an array of rejected values:

runTasks(taskArray)
  .then(function(results){
    // do whatever you want here
    // results.success is an array of the returned/resolved values
    // results.fail is an array of the rejected/thrown values
  })
granmoe
  • 312
  • 1
  • 11
  • `Bluebird.attempt(promise)` doesn't make much sense, does it? – Bergi Nov 25 '15 at 00:03
  • It does in my case because not every function in the array returns a promise. – granmoe Nov 26 '15 at 17:39
  • "*not every function in the array*" - huh? I thought we'd have an array of promises, not of functions? The names of your variables suggest that as well. Maybe you meant `Promise.resolve`? – Bergi Nov 26 '15 at 18:09
  • Sorry for the confusing variable names. I've edited the answer to fix them. After considering your comments, I think I should make a change to the code. The array of functions are functions that either throw or return values that will become promise resolutions. So I think I should wrap those functions in Promise.method instead and then replace return Bluebird.attempt(task) with return task(). I'll edit my answer to provide more context. – granmoe Nov 27 '15 at 16:44
  • Yeah, or that. `Promise.try` (`.attempt`) was fine if you have a `taskArray`, but the OP really has an array of promises. – Bergi Nov 28 '15 at 11:11