考虑下面以串行/顺序方式读取文件数组的代码。readFiles返回一个承诺,只有在顺序读取所有文件后才会解析这个承诺。

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  return new Promise((resolve, reject) => {
    var readSequential = function(index) {
      if (index >= files.length) {
        resolve();
      } else {
        readFile(files[index]).then(function() {
          readSequential(index + 1);
        }).catch(reject);
      }
    };

    readSequential(0); // Start with the first file!
  });
};

上面的代码可以工作,但是我不喜欢为了使事情按顺序发生而进行递归。是否有一种更简单的方法可以重写这段代码,这样我就不必使用奇怪的readSequential函数了?

最初我尝试使用Promise。但是这会导致所有的readFile调用并发发生,这不是我想要的:

var readFiles = function(files) {
  return Promise.all(files.map(function(file) {
    return readFile(file);
  }));
};

当前回答

使用Array.prototype。Reduce,并记住将承诺包装在函数中,否则它们将已经在运行!

// array of Promise providers

const providers = [
  function(){
     return Promise.resolve(1);
  },
  function(){
     return Promise.resolve(2);
  },
  function(){
     return Promise.resolve(3);
  }
]


const inSeries = function(providers){

  const seed = Promise.resolve(null); 

  return providers.reduce(function(a,b){
      return a.then(b);
  }, seed);
};

很好很简单…… 您应该能够为性能等重新使用相同的种子。

在使用reduce时,防止空数组或只有一个元素的数组是很重要的,所以这个技术是你最好的选择:

   const providers = [
      function(v){
         return Promise.resolve(v+1);
      },
      function(v){
         return Promise.resolve(v+2);
      },
      function(v){
         return Promise.resolve(v+3);
      }
    ]

    const inSeries = function(providers, initialVal){

        if(providers.length < 1){
            return Promise.resolve(null)
        }

        return providers.reduce((a,b) => a.then(b), providers.shift()(initialVal));
    };

然后像这样叫它:

inSeries(providers, 1).then(v => {
   console.log(v);  // 7
});

其他回答

2017年更新:如果环境支持,我会使用异步函数:

async function readFiles(files) {
  for(const file of files) {
    await readFile(file);
  }
};

如果你愿意,你可以延迟读取文件,直到你需要使用异步生成器(如果你的环境支持它):

async function* readFiles(files) {
  for(const file of files) {
    yield await readFile(file);
  }
};

更新:在第二个想法-我可能会用一个for循环代替:

var readFiles = function(files) {
  var p = Promise.resolve(); // Q() in q

  files.forEach(file =>
      p = p.then(() => readFile(file)); 
  );
  return p;
};

或者更确切地说,用reduce:

var readFiles = function(files) {
  return files.reduce((p, file) => {
     return p.then(() => readFile(file));
  }, Promise.resolve()); // initial
};

在其他承诺库(如when和Bluebird)中,您有用于此的实用程序方法。

例如,蓝鸟将是:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));

var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param

readAll.then(function(allFileContents){
    // do stuff to read files.
});

尽管现在没有理由不使用async await。

我能想到的最好的解决办法就是蓝鸟承诺。你可以执行Promise.resolve(files).each(fs.readFileAsync);这保证了承诺是按顺序被解决的。

我的答案基于https://stackoverflow.com/a/31070150/7542429。

Promise.series = function series(arrayOfPromises) {
    var results = [];
    return arrayOfPromises.reduce(function(seriesPromise, promise) {
      return seriesPromise.then(function() {
        return promise
        .then(function(result) {
          results.push(result);
        });
      });
    }, Promise.resolve())
    .then(function() {
      return results;
    });
  };

该解决方案以Promise.all()等数组的形式返回结果。

用法:

Promise.series([array of promises])
.then(function(results) { 
  // do stuff with results here
});

如果你愿意,你可以用reduce来做出一个连续的承诺,例如:

[2,3,4,5,6,7,8,9].reduce((promises, page) => {
    return promises.then((page) => {
        console.log(page);
        return Promise.resolve(page+1);
    });
  }, Promise.resolve(1));

它总是按顺序工作。

我不得不运行大量的顺序任务,并使用这些答案来伪造一个函数,将照顾处理任何顺序任务…

function one_by_one(objects_array, iterator, callback) {
    var start_promise = objects_array.reduce(function (prom, object) {
        return prom.then(function () {
            return iterator(object);
        });
    }, Promise.resolve()); // initial
    if(callback){
        start_promise.then(callback);
    }else{
        return start_promise;
    }
}

该函数接受2个参数+ 1个可选参数。第一个参数是我们将要处理的数组。第二个参数是任务本身,一个返回承诺的函数,只有当这个承诺解决时,下一个任务才会开始。第三个参数是在所有任务完成后运行的回调。如果没有传递回调,则函数返回它创建的promise,以便我们可以处理结束。

下面是一个用法示例:

var filenames = ['1.jpg','2.jpg','3.jpg'];
var resize_task = function(filename){
    //return promise of async resizing with filename
};
one_by_one(filenames,resize_task );

希望它能节省一些时间…