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

考虑下面以串行/顺序方式读取文件数组的代码。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.all,但这导致所有readFile调用并发发生,这就是我想要的:

var readFiles = function(files) {
return Promise.all(files.map(function(file) {
return readFile(file);
}));
};
314234 次浏览

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

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

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

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

更新:在第二个想法-我可能会用一个for循环代替:

var readFiles = function(files) {
var p = Promise.resolve(); // Q() in q


files.forEach(file =>
p = p.then(() => readFile(file));
);
return p;
};

或者更确切地说,用reduce:

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

虽然今天确实没有理由使用async await。

我能想出的最好的解决方案是bluebird承诺。你可以只做Promise.resolve(files).each(fs.readFileAsync);,它保证承诺按顺序被解析。

下面是我喜欢以串联方式运行任务的方式。

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

我在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编写移动应用程序时。

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

给出的答案是:

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

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

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

你很容易认为你必须使用Promise.all(arr.map(fn)).then(fn)(我已经做过很多次了)或Promise lib的花哨糖(尤其是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+,国外的承诺就会被同化。

这里的关键是还原的sequence承诺,它对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.sequence实现:

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

这个解决方案不仅适用于任何大小的序列,而且你可以很容易地向它添加数据节流和负载均衡

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

希望它能节省一些时间…

这个问题很老了,但我们生活在ES6和函数式JavaScript的世界里,所以让我们看看如何改进。

因为承诺会立即执行,所以我们不能只创建一个承诺数组,它们会同时发射。

相反,我们需要创建一个返回承诺的函数数组。然后依次执行每个函数,然后在其中启动promise。

我们可以用一些方法来解决这个问题,但我最喜欢的方法是使用reduce

reduce与承诺结合使用有点棘手,所以我在下面将这一行分解成一些更小的易于理解的内容。

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

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

一旦执行了所有的promise,返回的promise将包含每个promise的所有结果的数组。

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

你可以使用这个函数来获取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();
}

它之所以有效,是因为承诺工厂直到被要求才创建承诺。它的工作方式与then函数相同——事实上,它是一样的!

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

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

我使用以下代码扩展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));

它总是按顺序工作。

另外的例子

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包做同样的事情。

您的方法还不错,但它确实有两个问题:它会吞下错误,并且使用显式承诺构造反模式。

你可以解决这两个问题,并使代码更干净,同时仍然采用相同的一般策略:

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

该解决方案以Promise.all()等数组的形式返回结果。

用法:

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

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

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)上测试。

我真的很喜欢@joelnet的回答,但对我来说,这种编码风格有点难以消化,所以我花了几天时间试图弄清楚如何以更可读的方式表达相同的解决方案,这就是我的想法,只是使用了不同的语法和一些注释。

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

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

如果其他人在执行CRUD操作时需要一种有保证的严格顺序的方法来解析promise,您也可以使用以下代码作为基础。

只要你在调用每个函数之前添加'return',描述一个Promise,并使用这个例子作为基础,下一个.then()函数调用将在前一个函数完成后consistent启动:

getRidOfOlderShoutsPromise = () => {
return readShoutsPromise('BEFORE')
.then(() => {
return deleteOlderShoutsPromise();
})
.then(() => {
return readShoutsPromise('AFTER')
})
.catch(err => console.log(err.message));
}


deleteOlderShoutsPromise = () => {
return new Promise ( (resolve, reject) => {
console.log("in deleteOlderShouts");
let d = new Date();
let TwoMinuteAgo = d - 1000 * 90 ;
All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
if (err) reject();
console.log("DELETED OLDs at "+d);
resolve();
});
});
}


readShoutsPromise = (tex) => {
return new Promise( (resolve, reject) => {
console.log("in readShoutsPromise -"+tex);
All_Shouts
.find({})
.sort([['dateTime', 'ascending']])
.exec(function (err, data){
if (err) reject();
let d = new Date();
console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
resolve(data);
});
});
}

使用Async/Await(如果你有ES7的支持)

function downloadFile(fileUrl) { ... } // This function return a Promise


async function main()
{
var filesList = [...];


for (const file of filesList) {
await downloadFile(file);
}
}

(你必须使用for循环,而不是forEach循环,因为async/await在forEach循环中运行有问题)

没有Async/Await(使用Promise)

function downloadFile(fileUrl) { ... } // This function return a Promise


function downloadRecursion(filesList, index)
{
index = index || 0;
if (index < filesList.length)
{
downloadFile(filesList[index]).then(function()
{
index++;
downloadRecursion(filesList, index); // self invocation - recursion!
});
}
else
{
return Promise.resolve();
}
}


function main()
{
var filesList = [...];
downloadRecursion(filesList);
}

正如Bergi所注意到的,我认为最好的和明确的解决方案是使用蓝鸟。每个,代码如下:

const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);

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

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

证明:https://jsbin.com/nulafus/1/edit?js,console

数组push和pop方法可用于承诺序列。当你需要额外的数据时,你也可以推出新的承诺。这是代码,我将使用在React无限加载器加载页面序列。

var promises = [Promise.resolve()];


function methodThatReturnsAPromise(page) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`Resolve-${page}! ${new Date()} `);
resolve();
}, 1000);
});
}


function pushPromise(page) {
promises.push(promises.pop().then(function () {
return methodThatReturnsAPromise(page)
}));
}


pushPromise(1);
pushPromise(2);
pushPromise(3);

使用现代ES:

const series = async (tasks) => {
const results = [];


for (const task of tasks) {
const result = await task;


results.push(result);
}


return results;
};


//...


const readFiles = await series(files.map(readFile));

首先,你需要明白承诺是在创建时执行的 例如,如果你有一个代码

["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.all的样式:

  • 返回.then()回调中的结果数组。

  • 如果某个承诺失败,它将立即在.catch()回调中返回。

const promiseEach = (arrayOfTasks) => {
let results = []
return new Promise((resolve, reject) => {
const resolveNext = (arrayOfTasks) => {
// If all tasks are already resolved, return the final array of results
if (arrayOfTasks.length === 0) return resolve(results)


// Extract first promise and solve it
const first = arrayOfTasks.shift()


first().then((res) => {
results.push(res)
resolveNext(arrayOfTasks)
}).catch((err) => {
reject(err)
})
}
resolveNext(arrayOfTasks)
})
}


// Lets try it 😎


const promise = (time, shouldThrowError) => new Promise((resolve, reject) => {
const timeInMs = time * 1000
setTimeout(()=>{
console.log(`Waited ${time} secs`)
if (shouldThrowError) reject(new Error('Promise failed'))
resolve(time)
}, timeInMs)
})


const tasks = [() => promise(1), () => promise(2)]


promiseEach(tasks)
.then((res) => {
console.log(res) // [1, 2]
})
// Oops some promise failed
.catch((error) => {
console.log(error)
})

注意tasks数组声明:

在这种情况下,不能像Promise.all那样使用以下符号:

const tasks = [promise(1), promise(2)]

我们必须使用:

const tasks = [() => promise(1), () => promise(2)]

原因是JavaScript在声明承诺后立即开始执行承诺。如果我们使用像Promise.all这样的方法,它只是检查所有这些方法的状态是fulfilledrejected,而不是开始执行本身。使用() => promise()停止执行,直到调用它。

(function() {
function sleep(ms) {
return new Promise(function(resolve) {
setTimeout(function() {
return resolve();
}, ms);
});
}


function serial(arr, index, results) {
if (index == arr.length) {
return Promise.resolve(results);
}
return new Promise(function(resolve, reject) {
if (!index) {
index = 0;
results = [];
}
return arr[index]()
.then(function(d) {
return resolve(d);
})
.catch(function(err) {
return reject(err);
});
})
.then(function(result) {
console.log("here");
results.push(result);
return serial(arr, index + 1, results);
})
.catch(function(err) {
throw err;
});
}


const a = [5000, 5000, 5000];


serial(a.map(x => () => sleep(x)));
})();

这里的关键是如何调用sleep函数。你需要传递一个函数数组,它本身返回一个promise,而不是一个promise数组。

nodejs中有promise-sequence

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

使用ES2016的async/await(可能还有ES2018的一些特性),这可以简化为这样的形式:

function readFile(file) {
... // Returns a promise.
}


async function readFiles(files) {
for (file in files) {
await readFile(file)
}
}

我还没见过其他答案这么简单。OP说不需要并行执行readFile。然而,对于这样的IO,在保持循环执行同步的同时,不阻塞单个文件的读取是有意义的(在读取所有文件之前,您不希望执行下一步)。由于我刚刚了解到这一点,并且对此感到有点兴奋,所以我将在readFiles的整体同步执行中分享readFile的并行异步执行方法。

async function readFiles(files) {
await Promise.all(files.map(readFile))
}

这难道不是一件美好的事情吗?

这是我在各种项目中使用的sequentially实现:

const file = [file1, file2, file3];
const fileContents = sequentially(readFile, files);


// somewhere else in the code:


export const sequentially = async <T, P>(
toPromise: (element: T) => Promise<P>,
elements: T[]
): Promise<P[]> => {
const results: P[] = [];
await elements.reduce(async (sequence, element) => {
await sequence;
results.push(await toPromise(element));
}, Promise.resolve());


return results;
};


有一个npm包承诺连续可以很好地做到这一点:

const Promise_serial = require('promise-serial');


const promises =
Array(15).fill()
.map((_, i) =>
() => new Promise(resolve => {
console.log('promise '+i+' start');
setTimeout(
() => {
console.log('promise '+i+' end');
resolve('output-'+i);
},
500
);
})
);




console.log('### Run promises in sequence')


Promise_serial(promises)


输出:

promise 0 start
promise 0 end
promise 1 start
promise 1 end
promise 2 start
promise 2 end
promise 3 start
promise 3 end
promise 4 start
promise 4 end
promise 5 start
promise 5 end
promise 6 start
promise 6 end
promise 7 start
promise 7 end


... etc


您还可以批处理或并行化它们。

看:https://www.npmjs.com/package/promise-serial

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

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

function task1() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(console.log("task 1"))
}, 3000)
})
}


function task2() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(console.log("task 2"))
}, 1000)
})
}


function seqTasks(){
task1()
.then(() => task2())
}


seqTasks();

下面是我的Angular/TypeScript方法,使用RxJS:

  1. 给定一个URL字符串数组,使用from函数将其转换为一个可观察对象。
  2. 使用pipe来包装Ajax请求、即时响应逻辑、任何所需的延迟和错误处理。
  3. pipe内部,使用concatMap序列化请求。否则,使用Javascript forEachmap将同时发出请求。
  4. 使用RxJS ajax进行调用,并在每次调用返回后添加所需的延迟。

工作示例:https://stackblitz.com/edit/rxjs-bnrkix?file=index.ts

代码看起来像这样(我留下了一些额外的内容,这样你可以选择保留或丢弃什么):

import { ajax } from 'rxjs/ajax';
import { catchError, concatMap, delay, from, of, map, Observable } from 'rxjs';


const urls = [
'https://randomuser.me/api/',
'https://randomuser.me/api/',
'https://randomuser.me/api/',
];
const delayAfterCall = 500;


from(urls)
.pipe(
concatMap((url: string) => {
return ajax.getJSON(url).pipe(
map((response) => {
console.log('Done! Received:', response);
return response;
}),
catchError((error) => {
console.error('Error: ', error);
return of(error);
}),
delay(delayAfterCall)
);
})
)
.subscribe((response) => {
console.log('received email:', response.results[0].email);
});

我想重复很多人说过的,解决这个问题的最好方法是使用async/await函数。我想重申这个解决方案,但是,也指出另一个解决方案,其中async/await不存在:

function readFile(file) {
return new Promise(function (resolve, reject) {
console.log(`Simulate reading of file ${file}`);
setTimeout(resolve, 1000);
} );
}


(async function() {
let files = [ "file1.txt", "file2.txt", "file3.txt" ];
for (let file of files)
await readFile(file);
})();

对于不支持async/await的JavaScript环境,我们可以选择使用https://babeljs.io来编译上面的代码,但要使用babel-plugin-transform-async-to-generator这样的插件。下面是使用插件的v6.24.1生成的:

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }


function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }


function readFile(file) {
return new Promise(function (resolve, reject) {
console.log(`Simulate reading of file ${file}`);
setTimeout(resolve, 1000);
});
}


_asyncToGenerator(function* () {
let files = ["file1.txt", "file2.txt", "file3.txt"];
for (let file of files)
yield readFile(file);
})();

如果你研究一下_asyncToGenerator()中发生了什么,你会发现它正在使用递归使用Promise链。您可以通过函数生成器提供Promise,因此,您可以专注于业务逻辑。