陷阱中的Await在forEach中失败

时间:2018-05-30 00:42:28

标签: javascript foreach async-await try-catch

我有一个类似于数组的结构,它暴露了异步方法。异步方法调用包含try-catch块,这些块在捕获的错误的情况下会显示更多的异步方法。我想了解为什么forEachasync / await不能很好地协作。

let items = ['foo', 'bar', 'baz'];

// Desirable behavior
processForLoop(items);
/* Processing foo
 * Resolved foo after 3 seconds.
 * Processing bar
 * Resolved bar after 3 seconds.
 * Processing baz
 * Resolved baz after 3 seconds.
 */

// Undesirable behavior
processForEach(items);
/* Processing foo
 * Processing bar
 * Processing baz
 * Resolved foo after 3 seconds.
 * Resolved bar after 3 seconds.
 * Resolved baz after 3 seconds.
 */

async function processForLoop(items) {
    for(let i = 0; i < items.length; i++) {
        await tryToProcess(items[i]);
    }
}

async function processForEach(items) {
    items.forEach(await tryToProcess);
}

async function tryToProcess(item) {
    try {
        await process(item);
    } catch(error) {
        await resolveAfter3Seconds(item);
    }
}

// Asynchronous method
// Automatic failure for the sake of argument
function process(item) {
    console.log(`Processing ${item}`);
    return new Promise((resolve, reject) =>
        setTimeout(() => reject(Error('process error message')), 1)
    );
}

// Asynchrounous method
function resolveAfter3Seconds(x) {
    return new Promise(resolve => setTimeout(() => {
        console.log(`Resolved ${x} after 3 seconds.`);
        resolve(x);
    }, 3000));
}

2 个答案:

答案 0 :(得分:2)

无法像forEach一样使用await - forEach无法在串行中运行异步迭代,只能并行运行(即使这样,map也会{ {1}}会更好)。相反,如果您想使用数组方法,请使用Promise.allreduce解析前一次迭代的Promise:

await

另请注意,如果函数中的唯一let items = ['foo', 'bar', 'baz']; processForEach(items); async function processForLoop(items) { for (let i = 0; i < items.length; i++) { await tryToProcess(items[i]); } } async function processForEach(items) { await items.reduce(async(lastPromise, item) => { await lastPromise; await tryToProcess(item); }, Promise.resolve()); } async function tryToProcess(item) { try { await process(item); } catch (error) { await resolveAfter3Seconds(item); } } // Asynchronous method // Automatic failure for the sake of argument function process(item) { console.log(`Processing ${item}`); return new Promise((resolve, reject) => setTimeout(() => reject(Error('process error message')), 1) ); } // Asynchrounous method function resolveAfter3Seconds(x) { return new Promise(resolve => setTimeout(() => { console.log(`Resolved ${x} after 3 seconds.`); resolve(x); }, 3000)); }就在函数返回之前,您也可以只返回Promise本身,而不是让函数为await

答案 1 :(得分:1)

  

我想了解为什么forEachasync / await不能很好地配合。

当我们认为async只是返回承诺的函数的语法糖时,这会更容易。

items。forEach(f)期望函数f作为参数,它在返回之前在每个项目上执行一次。它忽略了f的返回值。

items.forEach(await tryToProcess)无效等同于Promise.resolve(tryToProcess).then(ttp => items.forEach(ttp))

items.forEach(tryToProcess)在功能上没有区别。

现在tryToProcess会返回一个承诺,但forEach会忽略返回值,正如我们所提到的那样,所以它忽略了这个承诺。这是坏消息,并且可能导致未处理的拒绝错误,因为所有承诺链都应该使用catch返回或终止以正确处理错误。

这个错误等同于遗忘await。不幸的是,没有array.forEachAwait()

项。map(f)稍好一些,因为它会从f的返回值中创建一个数组,在tryToProcess的情况下会给我们一个数组承诺。例如。我们可以这样做:

await Promise.all(items.map(tryToProcess));

...但每个项目的所有tryToProcess次调用都会相互执行

重要的是,map并行运行它们。 Promise.all只是等待完成的一种方式。

通常......

我总是在for of个函数中使用forEach代替async

for (item of items) {
  await tryToProcess(item);
}

...即使循环中没有await,以防我稍后添加一个,以避免这种脚枪。