如何 return 将 Promise 值作为数组累积 return 到 Array.prototype.reduce() 之后的 .then()?

How to return accumulated returned Promise values as array to .then() following Array.prototype.reduce()?

鉴于此模式

someArray.reduce(function(p, item) {
  return p.then(function() {
    return someFunction(item);
  });
}, $.Deferred().resolve()).then(function() {
  // all done here
  // access accumulated fulfilled , rejected `Promise` values
}, function err() {

});

在调用 .reduce() 之后,有哪些方法可以 return 将已完成、已拒绝的 Promise 对象的累积值作为数组 .then(fulfilled)

function someFunction(index) {
  console.log("someFunction called, index = " + index);
  var $deferred = $.Deferred();

  window.setTimeout(function() {
    $deferred.resolve();
  }, 2000);

  return $deferred.promise();
}
   
var someArray = [1,2,3,4,5];

someArray.reduce(function(p, item) {
  return p.then(function() {
    return someFunction(item);
  });
}, $.Deferred().resolve()).then(function(data) {
  // all done here
  console.log(data, arguments) // `undefined` , `[]`
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>

根据您要执行的操作的具体情况,有多种可能的策略:这是一个选项:

someArray.reduce(function(p, item) {
  return p.then(function(array) {
    return someFunction(item).then(function(val) {
        array.push(val);
        return array;
    });
  });
}, $.Deferred().resolve([])).then(function(array) {
  // all done here
  // accumulated results in array
}, function(err) {
  // err is the error from the rejected promise that stopped the chain of execution
});

工作演示:http://jsfiddle.net/jfriend00/d4q1aaa0/


仅供参考,Bluebird Promise 库(这是我通常使用的)有 .mapSeries() 是为此模式构建的:

var someArray = [1,2,3,4];

Promise.mapSeries(someArray, function(item) {
    return someFunction(item);
}).then(function(results) {
    log(results);
});

工作演示:http://jsfiddle.net/jfriend00/7fm3wv7j/

一种可能的解决方案:

var $j = function(val, space) {
  return JSON.stringify(val, null, space || '')
}
var log = function(val) {
  document.body.insertAdjacentHTML('beforeend', '<div><pre>' + val + '</div></pre>')
}

var async = function(cur){
  var pro = new Promise(function(resolve, reject) {

        log('loading : ' + cur.name);
        
        // we simualate the loading
        setTimeout(function() {
          if(cur.name === 'file_3.js'){
            reject(cur.name);
          }
          resolve(cur.name);
        }, 1 * 1000);

      });

      return pro;
}

var files = '12345'.split('').map(function(v) {
  return {
    name: 'file_' + v + '.js', 
  }
});


var listed = files.reduce(function(t,v){

  t.p = t.p.then( function(){
    return async( v )
      .then(function(rep){
      
               t.fulfilled.push(rep);
               log('fulfilled :' + rep); 
               return rep;
      
      } , function(rep){
               
               t.rejected.push(rep);
               log('-----| rejected :' + rep); 
               return rep;
      }
   ).then(function(val){ t.treated.push(val) })
  });
  
  return t;
  
} , {p : Promise.resolve() , treated : [] , fulfilled : [] , rejected : [] } )


listed.p.then( function(){ 
  log( 'listed : ' + $j( listed , '   ' )) 
});

在 @jfriend00 演示的方法旁边,您使用一个数组解析每个承诺,在该数组中将当前结果附加到所有以前的结果,您还可以使用并行执行模式中已知的承诺数组 Promise.all.map.

为此,您必须将您在 reduce 步骤中创建的所有承诺放在一个数组中。之后,您可以对该数组调用 Promise.all 以等待所有结果。这种方法的优点是您的代码只需要最少的调整,因此您可以轻松地在需要结果的版本和不需要结果的版本之间来回切换。
为了将每个步骤的结果收集到一个数组中,我们使用了 reduce 的变体,即 scan 并 return 一个数组(如 map)而不是最新结果:

Array.prototype.scan = function scanArray(callback, accumulator) {
    "use strict";
    if (this == null) throw new TypeError('Array::scan called on null or undefined');
    if (typeof callback !== 'function') throw new TypeError(callback+' is not a function');

    var arr = Object(this),
        len = arr.length >>> 0,
        res = [];
    for (var k = 0; k < len; k++)
        if (k in arr)
            res[k] = accumulator = callback(accumulator, arr[k], k, arr);
    return res;
};

模式现在看起来像

Promise.all(someArray.scan(function(p, item) {
    return p.then(function() {
       return someFunction(item);
    });
}, Promise.resolve())).then(…)

(对于 jQuery,将 Promise.resolve 替换为 $.Deferred().resolve(),将 Promise.all 替换为 $.when.apply($, …)