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

当前回答

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

["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。

其他回答

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

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

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>

我使用以下代码扩展Promise对象。它处理承诺的拒绝并返回一个结果数组

Code

/*
    Runs tasks in sequence and resolves a promise upon finish

    tasks: an array of functions that return a promise upon call.
    parameters: an array of arrays corresponding to the parameters to be passed on each function call.
    context: Object to use as context to call each function. (The 'this' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
    return new Promise((resolve, reject)=>{

        var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
        var output = new Array(tasks.length + 1);
        var errorFlag = false;

        tasks.forEach((task, index) => {
            nextTask = nextTask.then(r => {
                output[index] = r;
                return task.apply(context, parameters[index+1]);
            }, e=>{
                output[index] = e;
                errorFlag = true;
                return task.apply(context, parameters[index+1]);
            });
        });

        // Last task
        nextTask.then(r=>{
            output[output.length - 1] = r;
            if (errorFlag) reject(output); else resolve(output);
        })
        .catch(e=>{
            output[output.length - 1] = e;
            reject(output);
        });
    });
};

例子

function functionThatReturnsAPromise(n) {
    return new Promise((resolve, reject)=>{
        //Emulating real life delays, like a web request
        setTimeout(()=>{
            resolve(n);
        }, 1000);
    });
}

var arrayOfArguments = [['a'],['b'],['c'],['d']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);


Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);

根据问题的标题“一个接一个地(即顺序地)解决承诺?”,我们可以理解为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.
    });
};

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

这是对如何以更通用的方式处理承诺序列的扩展,支持基于spex的动态/无限序列。序列实现:

var $q = require("q");
var spex = require('spex')($q);

var files = []; // any dynamic source of files;

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

function source(index) {
    if (index < files.length) {
        return readFile(files[index]);
    }
}

function dest(index, data) {
    // data = resolved data from readFile;
}

spex.sequence(source, dest)
    .then(function (data) {
        // finished the sequence;
    })
    .catch(function (error) {
        // error;
    });

这种解决方案不仅适用于任何大小的序列,而且还可以轻松地向其添加数据节流和负载平衡。

这里有很多答案,但我没有看到这个简单的解决方案:

await array.reduce(
  async (promise, member) => await myLongSequentialPromise(member),
  array[0]
)

证明:https://jsbin.com/nulafus/1/edit?js,控制台