David Z. David Z. - 3 months ago 8
Node.js Question

The scheduling order of Promise.then promises when grouped in Promise.all

I encountered a very interesting problem recently. The background is that I want to fire a bunch of http requests concurrently, and capture the result of each response in an array. Also, when all promises of requests are resolved, .
I tried two ways to implement it but sometimes got different results.

Look at the following code snippets (

httpHelper.get
just returns a BlueBird promise).
Solution A:

function solutionA() {
var requestOptions = [...]; // an array of request options
var promises = [];
var results = [];
_.forEach(requestOptions, function(requestOption){
var promise = httpHelper.get(requestOption).then(function singleThenCallBack(response){
//Using this solution, sometimes this code won't execute from some response, I guess it's because the Promise.all.then gets executed before this then.
results.push(response.body.result);
});
promises.push(promise);
});
return Promise.all(promises).then(function allThenCallBack(){
return results;
});
}


Solution B:

function solutionB() {
var requestOptions = [...]; // an array of request options
var promises = [];
var results = [];
_.forEach(requestOptions, function singleThenCallBack(requestOption){
var promise = httpHelper.get(requestOption);
promises.push(promise);
});
return Promise.all(promises).then(function allThenCallBack(responses){
_.forEach(responses, function(response){
results.push(response.body.result);
});
return results;
});
}


So the problem of using Solution A is that, sometimes the
singleThenCallBack
function won't be called on some response, so the
results
doesn't contain every result I should get.
Solution B always ensure that all results are push into array
results
. I guess the cause is the timing how the
then
for
Promise
and
Promise.all
is scheduled.
My question is that in Solution A, shouldn't the
promises
be the promises of the
Promise.then
s, which would ensure
singleThenCallBack
function for all responses should be called before
allThenCallBack
function is reached?

Hope someone could explain the reason of this behaviour to me. Thanks!

Edit:
So I tried to run some code to prove the promise chaining with following code:

var Promise = require("bluebird");

var promises = [];

var promise1 = new Promise(function(resolve, reject){
setTimeout(function() {
resolve(1);
}, 5000);
});

var thenPromise1 = promise1.then(function(value){
console.log("********* resolved promise1 *********: " + value);
console.log(promise1);
return 0.1;
}, console.log("*****This means then function is called synchronously."));

promises.push(thenPromise1);

thenPromise1.then(function(value){
console.log("********* resolved thenPromise1 *********: " + value);
console.log(thenPromise1);
});

var allPromise = Promise.all(promises);

allPromise.then(function(value){
console.log("********* resolved allPromise *********: " + value);
console.log(allPromise);
});

console.log("********* promise1 *********");
console.log(promise1);
console.log("********* thenPromise1 *********");
console.log(thenPromise1);
console.log("********* allPromise *********");
console.log(allPromise);

console.log("***********code end*************");


And the output is:

*****This means then function is called synchronously.
********* promise1 *********
{ _bitField: 1,
_fulfillmentHandler0: [Function],
_rejectionHandler0: undefined,
_promise0:
{ '2': 0,
'3':
{ _promise: [Object],
_values: [Object],
_length: 1,
_totalResolved: 0 },
_bitField: 2,
_fulfillmentHandler0: [Function],
_rejectionHandler0: undefined,
_promise0:
{ _bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_promise0: undefined,
_receiver0: undefined,
_trace: [Object] },
_receiver0: undefined,
_trace: { [Error] _parent: undefined, _promisesCreated: 0, _length: 1 } },
_receiver0: undefined,
_trace: { [Error] _parent: undefined, _promisesCreated: 0, _length: 1 } }
********* thenPromise1 *********
{ '2': 0,
'3':
{ _promise:
{ _bitField: 134217729,
_fulfillmentHandler0: [Function],
_rejectionHandler0: undefined,
_promise0: [Object],
_receiver0: undefined },
_values: [ [Circular] ],
_length: 1,
_totalResolved: 0 },
_bitField: 2,
_fulfillmentHandler0: [Function],
_rejectionHandler0: undefined,
_promise0:
{ _bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_promise0: undefined,
_receiver0: undefined,
_trace: { [Error] _parent: undefined, _promisesCreated: 0, _length: 1 } },
_receiver0: undefined,
_trace: { [Error] _parent: undefined, _promisesCreated: 0, _length: 1 } }
********* allPromise *********
{ _bitField: 134217729,
_fulfillmentHandler0: [Function],
_rejectionHandler0: undefined,
_promise0:
{ _bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_promise0: undefined,
_receiver0: undefined,
_trace: { [Error] _parent: undefined, _promisesCreated: 0, _length: 1 } },
_receiver0: undefined }
***********code end*************
********* resolved promise1 *********: 1
{ _bitField: 33554433,
_fulfillmentHandler0: [Function],
_rejectionHandler0: 1,
_promise0: undefined,
_receiver0: undefined,
_trace: { [Error] _parent: undefined, _promisesCreated: 0, _length: 1 } }
********* resolved thenPromise1 *********: 0.1
{ '2': 0,
'3':
{ _promise:
{ _bitField: 134217729,
_fulfillmentHandler0: [Function],
_rejectionHandler0: undefined,
_promise0: [Object],
_receiver0: undefined },
_values: [ [Circular] ],
_length: 1,
_totalResolved: 0 },
_bitField: 33554434,
_fulfillmentHandler0: [Function],
_rejectionHandler0: 0.1,
_promise0: undefined,
_receiver0: undefined,
_trace: { [Error] _parent: undefined, _promisesCreated: 0, _length: 1 } }
********* resolved allPromise *********: 0.1
{ _bitField: 167772161,
_fulfillmentHandler0: [Function],
_rejectionHandler0: [ 0.1 ],
_promise0: undefined,
_receiver0: undefined }


In the output, the
thenPromise1
object has the
_promise0
, which is the promise created by
thenPromise1.then(...)
. And the promise object after
'3'
is the promise that created by
Promise.all(...)
, which means this promise is always chained after the
thenPromise1
.
So I think the question was invalid and something was wrong in other parts.

Answer

This does not explain the problem of the OP but I does not fit into a comment.

That does not make sense. Both solutionA or solutionB should have the same result. Either all Promises are successfully resolved, and then results.length has to be equal to requestOptions.length or one or more of the httpHelper.get(requestOption) fail, and then also the Promise.all(promises) will be rejected and no result will be returned. So I'm pretty sure that the problem is anywhere else.

Beside that as you use bluebird you can write your code in a much clearer way (assuming that you use const Promise = require('bluebird'):

function solution() {
  var requestOptions = [...]; // an array of request options

  return Promise.all(requestOptions)  // pass all request options
    .map(function(requestOption) {  // for each option create a request and return its promise
        return httpHelper.get(requestOption);
    }) 
    .map(function(response) { // for each response return the response.body.result
       return response.body.result;
    }); // now the promise resolves to an array containing the just the response.body.result
}

solutionA()
.then(function( result ) {
   console.dir(result);
});

With ES6 you could use Arrow functions write it that way:

function solution() {
  var requestOptions = [...]; // an array of request options

  return Promise.all(requestOptions)  // pass all request options
    .map( requestOption => httpHelper.get(requestOption) )
    .map( response => response.body.result );
}