Promise.then 的调度顺序承诺在 Promise.all 中分组时

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

最近遇到一个很有意思的问题。背景是我想同时触发一堆http请求,并将每个响应的结果捕获到一个数组中。此外,当所有请求的承诺都得到解决时,. 我尝试了两种方法来实现它,但有时会得到不同的结果。

查看以下代码片段(httpHelper.get 只是 returns BlueBird 的承诺)。 解决方案 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;
  });
}

方案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;
  });
}

所以使用解决方案 A 的问题是,有时 singleThenCallBack 函数不会在某些响应中被调用,因此 results 不包含我应该得到的所有结果。 解决方案 B 始终确保将所有结果推入数组 results。我想原因是 PromisePromise.allthen 的时间安排。 我的问题是,在解决方案 A 中,promises 不应该是 Promise.then 的承诺,这将确保在 allThenCallBack 之前调用所有响应的 singleThenCallBack 函数功能达到了吗?

希望有人能向我解释这种行为的原因。谢谢!

编辑: 所以我尝试 运行 一些代码来证明使用以下代码链接的承诺:

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*************");

输出为:

*****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 }

在输出中,thenPromise1 对象具有 _promise0,这是 thenPromise1.then(...) 创建的承诺。而 '3' 之后的 promise 对象是 Promise.all(...) 创建的 promise,这意味着这个 promise 总是链接在 thenPromise1 之后。 所以我认为这个问题是无效的,其他部分有问题。

这并不能解释OP的问题,但我不适合发表评论。

这没有意义。 solutionAsolutionB 应该有相同的结果。要么所有 Promise 都成功解决,然后 results.length 必须等于 requestOptions.length 或一个或多个 httpHelper.get(requestOption) 失败,然后 Promise.all(promises) 也将被拒绝并且不会返回 result。所以我很确定问题出在其他地方。

除此之外,当您使用 bluebird 时,您可以以更清晰的方式编写代码(假设您使用 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);
});

在 ES6 中你可以使用箭头函数这样写:

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 );
}

初始承诺中的 .then() 处理程序将在 Promise.all() .then() 处理程序执行之前执行。这是因为在 .then() 处理程序被调用之前,原始承诺不会完成和解决(因为它可能 return 更高级别的承诺在解决之前必须等待的链式承诺)。但是,那些原始的 .then() 处理程序不会以可预测的顺序执行,因为您的所有操作都是 运行 并行的,这意味着您在 solutionA 中的 results 数组没有顺序。

我建议您像这样更改原始承诺的结果:

  _.forEach(requestOptions, function(requestOption){
     var promise = httpHelper.get(requestOption).then(function singleThenCallBack(response){
         // change resolved value to be the body.result
         return response.body.result;
     });
     promises.push(promise);
  });
  return Promise.all(promises);

但是,如果您使用的是 Bluebird,您也可以使用 Promise.map(),它结合了迭代和 Promise.all()

return Promise.map(requestOptions, function(item) {
    return httpHelper.get(item).then(function(response) {
        return response.body.result;
    });
});