解决承诺一个接一个(即按顺序)?

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

 var Q = require("q"); var readFile = function(file) { ... // Returns a promise. }; var readFiles = function(files) { var deferred = Q.defer(); var readSequential = function(index) { if (index >= files.length) { deferred.resolve(); } else { readFile(files[index]).then(function() { readSequential(index + 1); }); } }; readSequential(0); // Start! return deferred.promise; }; 

上面的代码工作的代码,但我不喜欢做recursion顺序发生的事情。 有没有更简单的方法,这个代码可以重写,所以我不必使用我奇怪的readSequential函数?

本来我试图使用Q.all ,但这导致所有的readFile调用同时发生,这不是我想要的:

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

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

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

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

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

更新:在第二个想法 – 我可能会使用一个for循环:

 var readFiles = function(files) { var p = Q(); // Promise.resolve() without Q files.forEach(function(file){ p = p.then(function(){ return readFile(file); }); // or .bind }); return p; }; 

或者更紧凑,减less:

 var readFiles = function(files) { return files.reduce(function(p, file) { return p.then(function(){ return readFile(file); }); },Q()); // 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. }); 

在Q中,你所拥有的就像你所能得到的一样好 – 你可以用Array.prototype.reduce来缩短它,并将其提取到一个通用的方法中。

如果你可以使用Q.async (你在节点上),事情会变得更好:

 Q.spawn(function* () { var results = []; for(var i = 0;i < files.length; i++){ results.push(yield readFile(files[i])); } console.log(results); }); 

只要记住用--harmony运行节点, 并记住它是实验性的

这是我更喜欢连续运行任务的方式。

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

这个问题很古老,但我们生活在一个ES6和function性JavaScript的世界里,所以让我们看看我们如何改进。

由于承诺立即执行,我们不能只是创build一个承诺的数组,他们都会并行地开火。

相反,我们需要创build一个返回promise的函数数组。 然后每个函数将被顺序执行,然后在里面启动promise。

我们可以通过几种方法解决这个问题,但是我最喜欢的方式是使用reduce

使用reduce与承诺相结合会有点棘手,所以我已经把一行划分成了一些较小的可消化咬痕。

这个函数的实质是使用reducePromise.resolve([])的初始值开始,或者包含一个空数组的promise。

这个承诺随后将作为promise传递给reduce方法。 这是顺序链接每个承诺的关键。 下一个执行的承诺是func ,当then触发时,结果被连接起来,然后返回promise,执行下一个promise函数的reduce循环。

一旦所有的承诺都执行完毕,返回的承诺将包含每个承诺的所有结果的数组。

ES6示例(单行)

 /* * serial executes Promises sequentially. * @param {funcs} An array of funcs that return promises. * @example * const urls = ['/url1', '/url2', '/url3'] * serial(urls.map(url => () => $.ajax(url))) * .then(console.log.bind(console)) */ const serial = funcs => funcs.reduce((promise, func) => promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([])) 

ES6示例(细分)

 // broken down to for easier understanding const concat = list => Array.prototype.concat.bind(list) const promiseConcat = f => x => f().then(concat(x)) const promiseReduce = (acc, x) => acc.then(promiseConcat(x)) /* * serial executes Promises sequentially. * @param {funcs} An array of funcs that return promises. * @example * const urls = ['/url1', '/url2', '/url3'] * serial(urls.map(url => () => $.ajax(url))) * .then(console.log.bind(console)) */ const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([])) 

用法:

 // first take your work const urls = ['/url1', '/url2', '/url3', '/url4'] // next convert each item to a function that returns a promise const funcs = urls.map(url => () => $.ajax(url)) // execute them serially serial(funcs) .then(console.log.bind(console)) 

要在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(function(file) { // Chain one computation onto the sequence sequence = sequence.then(function() { return performComputation(file); }).then(function(result) { doSomething(result) // Resolves for each file, one at a time. }); }) // This will resolve after the entire chain is resolved return sequence; } 

标准Node.js的简单实用程序承诺:

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

UPDATE

items-promise是一个可以使用的NPM包。

我不得不运行很多顺序的任务,并使用这些答案来伪造一个函数来处理任何连续的任务。

 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个可选项。 第一个参数是我们将要工作的数组。 第二个参数是任务本身,一个返回承诺的函数,只有当这个承诺解决时才会启动下一个任务。 第三个参数是所有任务完成后的callback。 如果没有callback通过,那么函数返回它创build的承诺,所以我们可以处理结束。

以下是一个使用示例:

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

希望能节省一些时间…

我能弄清楚的最好的解决scheme是bluebird承诺。 你可以做Promise.resolve(files).each(fs.readFileAsync); 这保证了承诺按顺序依次解决。

我的首选解决scheme

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

这里和其他发表的文章没有根本的区别,但是:

  • 将函数应用于系列中的项目
  • 解决了一系列结果
  • 不需要asynchronous/等待(支持仍然相当有限,大约2017年)
  • 使用箭头function; 好,简洁

用法示例:

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

testing目前的合理的Chrome(v59)和NodeJS(v8.1.2)。

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

创buildPromise.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.all和Promise.sequence以相同的方式被调用,但是具有不同的语义。

警告

承诺的顺序运行通常不是使用承诺的好方法。 使用Promise.all通常会更好,并让浏览器尽可能快地运行代码。 但是,它有真实的使用情况 – 例如,使用JavaScript编写移动应用程序时。

你可以使用这个函数获取promiseFactories List:

 function executeSequentially(promiseFactories) { var result = Promise.resolve(); promiseFactories.forEach(function (promiseFactory) { result = result.then(promiseFactory); }); return result; } 

Promise Factory只是简单的函数,返回一个Promise:

 function myPromiseFactory() { return somethingThatCreatesAPromise(); } 

这是有效的,因为承诺工厂在被要求前不会创造承诺。 它和当时的function一样 – 事实上,它是一样的!

你根本不想操作一系列的promise。 根据Promise规范,只要承诺被创build,它就开始执行。 所以你真正想要的是一组承诺工厂…

如果你想了解更多的承诺,你应该检查这个链接: https : //pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

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

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

如果你愿意,你可以使用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 inSequence(tasks) { return tasks.reduce((p, task) => p.then(task), Promise.resolve()) } 

说明

如果你有这些任务[t1, t2, t3] ,那么上面相当于Promise.resolve().then(t1).then(t2).then(t3) 。 这是减less的行为。

如何使用

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

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

我真的很喜欢@ joelnet的答案,但是对于我来说,这种编码风格有点难于消化,所以我花了几天的时间想弄清楚如何以更可读的方式expression同一个解决scheme,这是我的采取不同的语法和一些意见。

 // first take your work const urls = ['/url1', '/url2', '/url3', '/url4'] // next convert each item to a function that returns a promise const functions = urls.map((url) => { // For every url we return a new function return () => { return new Promise((resolve) => { // random wait in milliseconds const randomWait = parseInt((Math.random() * 1000),10) console.log('waiting to resolve in ms', randomWait) setTimeout(()=>resolve({randomWait, url}),randomWait) }) } }) const promiseReduce = (acc, next) => { // we wait for the accumulator to resolve it's promise return acc.then((accResult) => { // and then we return a new promise that will become // the new value for the accumulator return next().then((nextResult) => { // that eventually will resolve to a new array containing // the value of the two promises return accResult.concat(nextResult) }) }) }; // the accumulator will always be a promise that resolves to an array const accumulator = Promise.resolve([]) // we call reduce with the reduce function and the accumulator initial value functions.reduce(promiseReduce, accumulator) .then((result) => { // let's display the final value here console.log('=== The final result ===') console.log(result) }) 

在问题题目“依次解决承诺”的基础上,我们可以理解OP对结算承诺的顺序处理比序列化本身更感兴趣。

这个答案是提供:

  • 以certificate顺序调用对顺序处理响应不是必需的。
  • 为这个页面的访问者揭示可行的替代模式 – 包括OP,如果他在一年之后仍然感兴趣的话。
  • 尽pipeOP声称他不想同时发出呼叫,这可能是真实的情况,但同样可能是一个基于连续处理响应的愿望的假设,正如标题所暗示的。

如果真的不需要并发呼叫,请参阅本杰明·格鲁恩鲍姆(Benjamin Gruenbaum)的全面回答呼叫(等等)的答案。

但是,如果您对允许并发呼叫,然后按顺序处理响应的模式感兴趣(为了改进性能),请继续阅读。

很有可能认为你必须使用Promise.all(arr.map(fn)).then(fn) (正如我已经做了很多次)或者一个Promise lib的花式糖(特别是Bluebird's) )一个arr.map(fn).reduce(fn)模式将完成这项工作,其优点是:

  • 与任何promise lib一起工作 – 甚至是预先兼容的jQuery版本 – 只使用.then()
  • 提供了跳过错误或停止错误的灵活性,无论你想用一行模式。

这是写给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 promise。 用A + libs,外国的承诺会被同化。

这里的关键是减less的sequence承诺,顺序处理 readFile承诺,但不是它们的创build。

一旦你吸收了这些,当你意识到.map()阶段实际上是不必要的时候,这可能会让人有些兴奋! 并行调用和串行处理按照正确的顺序执行,可以通过reduce()单独实现,另外还具有进一步灵活性的优点:

  • 通过简单地移动一行,将并行asynchronous调用转换为串行asynchronous调用 – 在开发过程中可能有用。

这是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. }); }; 

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

这是为了扩展如何以更通用的方式处理一系列的promise,支持基于spex.sequence实现的dynamic/无限序列:

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

这个解决scheme不仅可以处理任何大小的序列,还可以轻松地为其添加数据调节和负载平衡 。

你的方法并不糟糕,但它确实有两个问题:它吞下错误,并采用显式承诺构造反模式。

您可以解决这两个问题,并使代码更清晰,同时仍采用相同的一般策略:

 var Q = require("q"); var readFile = function(file) { ... // Returns a promise. }; var readFiles = function(files) { var readSequential = function(index) { if (index < files.length) { return readFile(files[index]).then(function() { return readSequential(index + 1); }); } }; // using Promise.resolve() here in case files.length is 0 return Promise.resolve(readSequential(0)); // Start! }; 

我的答案基于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; }); }; 

这个解决scheme像Promise.all()这样的数组返回结果。

用法:

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