Ожидать в блоке catch не удается внутри forEach - PullRequest
0 голосов
/ 30 мая 2018

У меня есть массивоподобная структура, которая предоставляет асинхронные методы.Вызовы асинхронных методов содержат блоки 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.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 голосов
/ 30 мая 2018

Я бы хотел понять, почему forEach не очень хорошо играет с async / await.

Легче, если учесть, что async простосинтаксический сахар для функции, возвращающей обещание.

items. forEach (f) ожидает функцию f в качестве аргумента, которую она выполняет по каждому элементу по одному за один раз до своего возврата.Он игнорирует возвращаемое значение f.

items.forEach(await tryToProcess) - бессмысленный эквивалент Promise.resolve(tryToProcess).then(ttp => items.forEach(ttp))

и функционально не отличается от items.forEach(tryToProcess).

Now 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, на всякий случай я добавлю его позже, чтобы избежать этого ножного пистолета.

0 голосов
/ 30 мая 2018

Нет способа использовать forEach с await подобным образом - forEach не может запускать асинхронные итерации последовательно, только параллельно (и даже тогда map с Promise.all будет лучше).Вместо этого, если вы хотите использовать методы массива, используйте reduce и 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));
}

Также обратите внимание, что если только await в функции находится непосредственно перед возвратом функции, вы также можете просто вернуть само Обещание, вместо того, чтобы функция былаasync.

...