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

当前回答

下面是我比较喜欢的连续运行任务的方式。

function runSerial() {
    var that = this;
    // task1 is a function that returns a promise (and immediately starts executing)
    // task2 is a function that returns a promise (and immediately starts executing)
    return Promise.resolve()
        .then(function() {
            return that.task1();
        })
        .then(function() {
            return that.task2();
        })
        .then(function() {
            console.log(" ---- done ----");
        });
}

有更多任务的情况会怎样呢?10 ?

function runSerial(tasks) {
  var result = Promise.resolve();
  tasks.forEach(task => {
    result = result.then(() => task());
  });
  return result;
}

其他回答

首先,您需要了解承诺是在创建时执行的。 例如,如果你有一个代码:

["a","b","c"].map(x => returnsPromise(x))

您需要更改为:

["a","b","c"].map(x => () => returnsPromise(x))

然后,我们需要按顺序链接承诺:

["a", "b", "c"].map(x => () => returnsPromise(x))
    .reduce(
        (before, after) => before.then(_ => after()),
        Promise.resolve()
    )

执行after(),将确保只有在promise到期时才创建(并执行)promise。

nodejs中有承诺序列。

const promiseSequence = require('promise-sequence');
return promiseSequence(arr.map(el => () => doPromise(el)));

如果你愿意,你可以用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));

它总是按顺序工作。

只需使用.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 ();

根据问题的标题“一个接一个地(即顺序地)解决承诺?”,我们可以理解为OP更感兴趣的是对结算承诺的顺序处理,而不是顺序调用本身。

给出的答案是:

演示顺序调用对于响应的顺序处理是不必要的。 向本页的访问者展示可行的替代模式——包括OP,如果他在一年后仍然感兴趣的话。 尽管OP断言他不想同时拨打电话,这可能是真的,但同样也可能是基于标题所暗示的对连续处理响应的渴望的假设。

如果并发调用真的不需要,那么请参阅Benjamin Gruenbaum的回答,其中全面涵盖了顺序调用(等等)。

但是,如果您对允许并发调用然后依次处理响应的模式感兴趣(为了提高性能),那么请继续阅读。

你很容易认为你必须使用Promise.all(arr.map(fn)).then(fn)(我已经做过很多次了)或Promise库的花哨糖(尤其是Bluebird的),然而(这篇文章的功劳)arr.map(fn).reduce(fn)模式将完成这项工作,其优点是:

适用于任何承诺库——甚至是预兼容版本的jQuery——只使用.then()。 提供了灵活性,跳过错误或停止错误,无论你想用一行mod。

这就是,为Q写的。

var readFiles = function(files) {
    return files.map(readFile) //Make calls in parallel.
    .reduce(function(sequence, filePromise) {
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

注意:只有一个片段Q()是特定于Q的。对于jQuery,您需要确保readFile()返回jQuery承诺。有了A+,国外的承诺就会被同化。

这里的关键是还原的序列承诺,它对readFile承诺的处理进行排序,而不是对它们的创建进行排序。

一旦你理解了这一点,当你意识到.map()阶段实际上是不必要的时,可能会有点令人兴奋!整个工作,并行调用加上正确顺序的串行处理,可以通过reduce()单独实现,再加上进一步灵活的额外优势:

通过简单地移动一行,从并行异步调用转换为串行异步调用-在开发过程中可能有用。

这是Q。

var readFiles = function(files) {
    return files.reduce(function(sequence, f) {
        var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

这是基本模式。如果您还想向调用者交付数据(例如文件或它们的一些转换),则需要一个温和的变体。