У меня есть структура, подобная массиву, которая предоставляет асинхронные методы. Вызовы асинхронных методов содержат блоки try-catch, которые, в свою очередь, предоставляют больше асинхронных методов в случае обнаруженных ошибок. Я хотел бы понять, почему forEach плохо играет с 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.info(`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.info(`Resolved ${x} after 3 seconds.`);
resolve(x);
}, 3000));
}



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Невозможно использовать forEach с await таким образом - forEach не может запускать асинхронные итерации последовательно, только параллельно (и даже тогда map с Promise.all будет лучше). Вместо этого, если вы хотите использовать методы массива, используйте reduce и await с разрешением 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.info(`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.info(`Resolved ${x} after 3 seconds.`);
resolve(x);
}, 3000));
}Также обратите внимание, что если единственный await в функции находится непосредственно перед возвратом функции, вы также можете просто вернуть само обещание, а не использовать функцию async.
Это только в том случае, если вы хотите запускать итерации параллельно, но вы дали понять, что хотите запускать их последовательно, таким образом, метод reduce.
На самом деле нет причин предпочесть шаблон reducefor of внутри функций async.
I'd like to understand why
forEachdoesn't play nicely withasync/await.
Это проще, если учесть, что async - это просто синтаксический сахар для функции, возвращающей обещание.
items.для каждого (f) ожидает функцию f в качестве аргумента, которую она выполняет для каждого элемента по очереди перед возвратом. Он игнорирует возвращаемое значение f.
items.forEach(await tryToProcess) - это ерунда, эквивалентная Promise.resolve(tryToProcess).then(ttp => items.forEach(ttp))
и функционально ничем не отличается от items.forEach(tryToProcess).
Теперь tryToProcess возвращает обещание, но forEach игнорирует возвращаемое значение, как мы уже упоминали, поэтому игнорирует это обещание. Это плохая новость и может привести к необработанным ошибкам отклонения, поскольку все цепочки обещаний должны быть возвращены или завершены с помощью catch для правильной обработки ошибок.
Эта ошибка эквивалентна забвению await. К сожалению, array.forEachAwait() нет.
items.карта (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, на всякий случай я добавлю еще один позже, чтобы избежать этого ножного пистолета.
(и даже тогда
mapсPromise.allбудет лучше) ... В моем случае оба асинхронных метода включают ввод-вывод, поэтому, если я не ошибаюсь, использованиеPromise.allбыло бы плохой идеей. Спасибо, мне очень помогли.