基于 trincot 的出色回答,我编写了一个可重用的函数,该函数接受一个处理程序来运行数组中的每个项目。该函数本身返回一个Promise,允许您等待直到循环完成并且您传递的处理程序函数也可能返回一个Promise。
循环(项目,处理程序):Promise
我花了一些时间才把它弄好,但我相信下面的代码在很多 Promise 循环情况下都可以使用。
复制粘贴就绪代码:
// SEE https://stackoverflow.com/a/46295049/286685
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
用法
要使用它,将要循环的数组作为第一个参数调用它,将处理程序函数作为第二个参数调用它。不要为第三、第四和第五个参数传递参数,它们是在内部使用的。
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const items = ['one', 'two', 'three']
loop(items, item => {
console.info(item)
})
.then(() => console.info('Done!'))
高级用例
让我们看看处理函数、嵌套循环和错误处理。
处理程序(当前,索引,所有)
处理程序传递了 3 个参数。当前项、当前项的索引和被循环的完整数组。如果处理函数需要做异步工作,它可以返回一个Promise,循环函数将在开始下一次迭代之前等待Promise解决。您可以嵌套循环调用,一切都按预期工作。
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
return loop(test, (testCase) => {
console.info(testCase)
})
.then(testNext)
.catch(testFailed)
}))
.then(() => console.info('All tests done'))
错误处理
我看到的许多Promise循环示例在发生异常时都会崩溃。让这个函数做正确的事情非常棘手,但据我所知它现在正在工作。确保向任何内部循环添加一个 catch 处理程序,并在它发生时调用拒绝函数。例如:
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
loop(test, (testCase) => {
if (idx == 2) throw new Error()
console.info(testCase)
})
.then(testNext)
.catch(testFailed) // <--- DON'T FORGET!!
}))
.then(() => console.error('Oops, test should have failed'))
.catch(e => console.info('Succesfully caught error: ', e))
.then(() => console.info('All tests done'))
更新:NPM 包
自从写了这个答案,我把上面的代码放到了一个 NPM 包中。
安装
npm install --save for-async
进口
var forAsync = require('for-async'); // Common JS, or
import forAsync from 'for-async';
用法(异步)
var arr = ['some', 'cool', 'array'];
forAsync(arr, function(item, idx){
return new Promise(function(resolve){
setTimeout(function(){
console.info(item, idx);
// Logs 3 lines: `some 0`, `cool 1`, `array 2`
resolve(); // <-- signals that this iteration is complete
}, 25); // delay 25 ms to make async
})
})
有关更多详细信息,请参阅包自述文件。