Необработанный отказ от обещания?

Этот пример (repl.it) (от этот ответ) мне кажется, что он следует всем правилам в отношении обещаний. Тем не менее, его запуск регистрирует исключение, касающееся необработанного отклонения обещания, с соответствующим сообщением консоли. (Это также происходит в FF, Chrome и Node v10.)

Блок try / catch явно присутствует и завершает отклоненное обещание, так что же происходит и как я могу это исправить?

async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.info(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }

  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.info(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }

  try {
    const delay1 = res(3000)
    const delay2 = res(2000)
    const delay3 = rej(1000)

    const data1 = await delay1
    const data2 = await delay2
    const data3 = await delay3
  } catch (error) {
    console.info(`await finished`, Date.now() - start)
  }
}

example()

🤔 А знаете ли вы, что...
JavaScript позволяет создавать динамические и интерактивные веб-приложения.


4
805
1

Ответ:

Решено

Проблема в том, что в момент, когда вызов rej отклоняется, интерпретатор еще не дошел до строки, в которой await передает обещание, созданное rej, поэтому отклоненное обещание - это просто отклоненное обещание, а не Обещание, что текущий поток - awaiting:

try {
  const delay1 = res(3000)
  const delay2 = res(2000)
  const delay3 = rej(1000)

  const data1 = await delay1
  // The interpreter is paused on the line above when `rej` rejects
  const data2 = await delay2
  const data3 = await delay3

Таким образом, поведение такое же, как если бы отклоненное обещание было объявлено без обработчика catch. (Ошибки, выдаваемые Promises, будут обнаружены функцией async, только если они являются в момент, когда обещание отклоняет с await - в противном случае это просто приведет к необработанному отклонению обещания.)

Я бы предложил либо объявить обещания в тот же момент, когда вы их await:

const data1 = await res(3000)

(примечание: время вышеупомянутого метода не будет таким же, как у исходного кода)

или используйте await Promise.all для всех обещаний, это означает, что Promise, который интерпретатор в настоящее время использует, будет выбрасывать (и, таким образом, входить в блок await), как только один из обещаний отклоняет:

const [data1, data2, data3] = await Promise.all([
  res(3000),
  res(2000),
  rej(1000)
]);

async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.info(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.info(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  try {
    const [data1, data2, data3] = await Promise.all([
      res(3000),
      res(2000),
      rej(1000),
    ]);
  } catch (error) {
    console.info(`error caught: await finished`, Date.now() - start)
  }
}

example()

Чтобы выполнить дополнительную работу во время выполнения трех обещаний и отловить ошибки из этих обещаний, а также из основного потока, передайте элемент четвертый в catch, IIFE, который выполняет дополнительную работу, которую вы хотите выполнить:

async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.info(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.info(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  try {
    const [data1, data2, data3] = await Promise.all([
      res(3000),
      res(2000),
      rej(1000),
      (() => {
        console.info('doing work...');
      })()
    ]);
  } catch (error) {
    console.info(`error caught: await finished`, Date.now() - start)
  }
}

example()