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

当前回答

要在ES6中简单地做到这一点:

function(files) {
  // Create a new empty promise (don't do that with real people ;)
  var sequence = Promise.resolve();

  // Loop over each file, and add on a promise to the
  // end of the 'sequence' promise.
  files.forEach(file => {

    // Chain one computation onto the sequence
    sequence = 
      sequence
        .then(() => performComputation(file))
        .then(result => doSomething(result)); 
        // Resolves for each file, one at a time.

  })

  // This will resolve after the entire chain is resolved
  return sequence;
}

其他回答

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

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(...)

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

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

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

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

希望它能节省一些时间…

我发现自己多次回到这个问题上,答案并没有给我我想要的,所以把这个放在这里给任何需要这个问题的人。

下面的代码执行顺序承诺执行(一个接一个),每一轮由多个调用组成:

async function sequence(list, cb) {
  const result = [];
  await list.reduce(async (promise, item) => promise
    .then(() => cb(item))
    .then((res) => result.push(res)
  ), Promise.resolve());
  return result;
}

展示:

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script> <script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script> <script type="text/babel"> function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function readFile(url, index) { console.log('Running index: ', index); // First action const firstTime = await axios.get(url); console.log('First API response: ', firstTime.data.activity); // Second action await sleep(1000); // Third action const secondTime = await axios.get(url); console.log('Second API response: ', secondTime.data.activity); // Fourth action await sleep(1000); return secondTime.data; } async function sequence(urls, fn) { const result = []; await urls.reduce(async (promise, url, index) => promise.then(() => fn(url, index)).then((res) => result.push(res)), Promise.resolve()); return result; } const urls = [ 'https://www.boredapi.com/api/activity', 'https://www.boredapi.com/api/activity', 'https://www.boredapi.com/api/activity', ]; (async function init() { const result = await sequence(urls, readFile); console.log('result', result); })() </script>