Невозможно перехватить ошибку в ожидании цикла for

#node.js #async-await

#node.js #асинхронный-ожидание

Вопрос:

Я читал о «for await цикла», как описано в MDN. Это выглядит впечатляюще, поэтому я немного поиграл с некоторым кодом, но, к моему удивлению, я не могу перехватить ошибки, возникающие там.

 'use strict';

const main = async() => {
  const bar = [];
  bar.push(new Promise((res) => { setTimeout(() => res(1), 1200); }));
  bar.push(new Promise((res) => { setTimeout(() => res(2), 800); }));
  bar.push(new Promise((res, rej) => { setTimeout(() => rej(3), 200); }));

  try {
    for await (const foo of bar) {
      console.log('hey', foo);
    }
  } catch (err) {
    console.error('err', err);
  }
};

main();
 

Мой результат в основном такой, как я ожидаю. Но я не могу понять, почему я получаю необработанное promiserejection? Разве я даже не уловил эту ошибку?



gt; node await-loop.js
(node:10704) UnhandledPromiseRejectionWarning: 3
(node:10704) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:10704) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
hey 1
hey 2
err 3
(node:10704) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

Может кто-нибудь, пожалуйста, помочь мне понять и написать правильную крылатую фразу здесь? Я что-то упускаю?

Ответ №1:

Необработанный отказ promiserejection?

Разве я даже не уловил эту ошибку?

Теоретически, да. (Ну, для этого и нужен блок catch, верно?)

Технически нет.

 Main Stack
|  try             |
|                  |
|    await bar[0]  | <-- waiting to resolve till sometime after 1200
|                  |
|  catch           |
|                  |
 

После (приблизительно) 200 миллис:

bar [2] отклоняется и помещается в очередь, но await не выдал, потому что он все еще ожидал выхода bar [0], что происходит только после 1200 миллисекунд

Итак, на момент отклонения строки [2] ни catch один блок не был доступен для обработки rejected promise . Посмотрите на приведенный выше стек и обратите внимание, что try..catch и bar[2] не находятся в одном и том же стеке на момент отклонения, ну, по крайней мере, для компилятора (или концептуально для javascript).

Но вы заметили: err 3 который блок catch улавливает, когда, наконец, ожидаются результаты bar[2]

Если вы измените порядок обещаний и поместите отклоненное обещание в качестве первого элемента в массив, вы увидите, что блок catch сработал хорошо.

Или ожидание по-другому:

 // unhandled rejection
try {
  await bar[0]
  await bar[1]
  await bar[2]
} catch (e) {
  console.error('error', e)
}

// Catch successfull
try {
  await bar[2]
  await bar[0]
  await bar[1]
} catch (e) {
  console.error('error', e)
}
 

Обновить:

Лучшим подходом было бы использовать Promise.all , чтобы он быстро отказывал.

 try {
  const datas = await Promise.all(bar)
} catch (e) {
  console.error(e)
}
 

Комментарии:

1. Спасибо за ваш ответ! Ваше решение не является практически, поскольку мне нужно на самом деле знать , какая асинхронная функция выдает ошибку (или, в данном случае, отклоняет обещание). Вы не возражаете против решения перехвата этой ошибки без перестановки моих вызовов?

2. Еще раз благодарю вас за обновление вашего ответа. Но я думаю, вы не уловили моего подхода. На самом деле я пытаюсь поймать ошибку при использовании for await of цикла. Если это возможно, я бы использовал Promise.all , конечно. Мой пример должен только продемонстрировать, что необходимо для воспроизведения моей ситуации. Я все еще хочу использовать for await of цикл, перебирающий асинхронные функции и все еще способный улавливать ошибки, если они возникают.

3. Как я уже сказал в решении, это было бы невозможно. async/await это просто синтаксический сахар. за сценой, которую он использует generators . Во время ожидания обещания для yield какого-либо другого обещания в массиве мог произойти сбой, поэтому в функции генератора не было бы блока catch в области видимости для обработки отклонения

Ответ №2:

for await of Цикл — это просто сокращенный синтаксис для синхронного for of цикла с символом await в теле цикла. Таким образом, вы можете достичь того, чего хотите, с помощью этого:

 'use strict';

const main = async() => {
  const bar = [];
  bar.push(new Promise((res) => { setTimeout(() => res(1), 1200); }));
  bar.push(new Promise((res) => { setTimeout(() => res(2), 800); }));
  bar.push(new Promise((res, rej) => { setTimeout(() => rej(3), 200); }));

  for (const foo of bar) {
    try { 
      const result = await foo;
      console.log('hey', result);
    } catch (err) {
      console.error('err', err);
    }
  }
};

main();
 

Кстати, если вы хотите знать, какое обещание вызвало ошибку, это Promise.allSettled может быть ближе к тому, что вы хотите, и более чистый код:

 'use strict';

const main = async() => {
  const bar = [];
  bar.push(new Promise((res) => { setTimeout(() => res(1), 1200); }));
  bar.push(new Promise((res) => { setTimeout(() => res(2), 800); }));
  bar.push(new Promise((res, rej) => { setTimeout(() => rej(3), 200); }));

  const results = await Promise.allSettled(bar);
  
  /* Outputs:
  [
  { status: 'fulfilled', value: 1 },
  { status: 'fulfilled', value: 2 },
  { status: 'rejected', reason: 3 }
]
  */
  console.log(results);
};

main();
 

Комментарии:

1. Спасибо за ваш ответ! Но решение проблемы — это просто обходной путь.