admin管理员组文章数量:1221589
I have an array-like structure that exposes async methods. The async method calls contain try-catch blocks which in turn expose more async methods in the case of caught errors. I'd like to understand why forEach
doesn't play nicely with async
/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));
}
I have an array-like structure that exposes async methods. The async method calls contain try-catch blocks which in turn expose more async methods in the case of caught errors. I'd like to understand why forEach
doesn't play nicely with async
/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));
}
Share
Improve this question
asked May 30, 2018 at 0:42
ParabolordParabolord
3123 silver badges14 bronze badges
0
2 Answers
Reset to default 15I'd like to understand why
forEach
doesn't play nicely withasync
/await
.
It's easier when we consider that async
is just syntactic sugar for a function returning a promise.
items.forEach(f) expects a function f
as argument, which it executes on each item one at at time before it returns. It ignores the return value of f
.
items.forEach(await tryToProcess)
is nonsense equivalent to Promise.resolve(tryToProcess).then(ttp => items.forEach(ttp))
and functionally no different from items.forEach(tryToProcess)
.
Now tryToProcess
returns a promise, but forEach
ignores the return value, as we've mentioned, so it ignores that promise. This is bad news, and can lead to unhandled rejection errors, since all promise chains should be returned or terminated with catch
to handle errors correctly.
This mistake is equivalent to forgetting await
. Unfortunately, there's no array.forEachAwait()
.
items.map(f) is a little better, since it creates an array out of the return values from f
, which in the case of tryToProcess
would give us an array of promises. E.g. we could do this:
await Promise.all(items.map(tryToProcess));
...but all tryToProcess
calls on each item would execute in parallel with each other.
Importantly, map
runs them in parallel. Promise.all
is just a means to wait for their completion.
As a rule...
I always use for of
instead of forEach
in async
functions:
for (const item of items) {
await tryToProcess(item);
}
...even when there's no await
in the loop, just in case I add one later, to avoid this foot-gun.
There is no way to use forEach
with await
like that - forEach
cannot run asynchronous iterations in serial, only in parallel (and even then, map
with Promise.all
would be better). Instead, if you want to use array methods, use reduce
and await
the resolution of the previous iteration's Promise:
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));
}
Also note that if the only await
in a function is just before the function returns, you may as well just return the Promise itself, rather than have the function be async
.
本文标签: javascriptAwait in catch block fails inside forEachStack Overflow
版权声明:本文标题:javascript - Await in catch block fails inside forEach - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1739368451a2160150.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论