考虑下面以串行/顺序方式读取文件数组的代码。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
});

其他回答

这是上面另一个答案的轻微变化。使用原生承诺:

function inSequence(tasks) {
    return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}

解释

如果你有这些任务[t1, t2, t3],那么上面的等价于Promise.resolve().then(t1).then(t2).then(t3)。这是约简的行为。

如何使用

首先你需要构造一个任务列表!任务是不接受实参的函数。如果需要将参数传递给函数,则使用bind或其他方法创建任务。例如:

var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)

我喜欢的解决方案:

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

它与这里发表的其他文章没有本质区别,但是:

将函数应用于序列中的项 解析为结果数组 不需要async/await(支持仍然非常有限,大约在2017年) 使用箭头函数;非常简洁

使用示例:

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

在当前合理的Chrome (v59)和NodeJS (v8.1.2)上测试。

另外的例子

const addTwo = async () => 2; const addThree = async (inValue) => new Promise((resolve) => setTimeout(resolve(inValue + 3), 2000)); const addFour = (inValue) => new Promise((res) => setTimeout(res(inValue + 4), 1000)); const addFive = async (inValue) => inValue + 5; // Function which handles promises from above async function sequenceAddition() { let sum = await [addTwo, addThree, addFour, addFive].reduce( (promise, currPromise) => promise.then((val) => currPromise(val)), Promise.resolve() ); console.log('sum:', sum); // 2 + 3 + 4 + 5 = 14 } // Run function. See console for result. sequenceAddition();

使用reduce()的通用语法

function sequence(tasks, fn) {
    return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}

更新

items-promise是一个准备使用NPM包做同样的事情。

只需使用.then(resPrevTask => nextTask())

(顺便说一下,下一个代码需要4秒。)

函数task1() { return new Promise((resolve) => { setTimeout(() => { 解决(console.log(任务1)) }, 3000) }) } 函数task2() { return new Promise((resolve) => { setTimeout(() => { 解决(console.log(任务2)) }, 1000) }) } 函数seqTasks () { task1 () .then(() => task2()) } seqTasks ();

我在Promise对象上创建了这个简单的方法:

创建并添加承诺。sequence方法添加到Promise对象

Promise.sequence = function (chain) {
    var results = [];
    var entries = chain;
    if (entries.entries) entries = entries.entries();
    return new Promise(function (yes, no) {
        var next = function () {
            var entry = entries.next();
            if(entry.done) yes(results);
            else {
                results.push(entry.value[1]().then(next, function() { no(results); } ));
            }
        };
        next();
    });
};

用法:

var todo = [];

todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);

// Invoking them
Promise.sequence(todo)
    .then(function(results) {}, function(results) {});

Promise对象的这个扩展最好的一点是,它与Promise的风格一致。的承诺。一切和承诺。序列以同样的方式调用,但具有不同的语义。

谨慎

连续运行承诺通常不是使用承诺的好方法。通常使用Promise会更好。所有这些,并让浏览器尽可能快地运行代码。然而,它也有一些实际的用例——例如在使用javascript编写移动应用程序时。