Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unhandled promise rejection?

This example (repl.it) (from this answer) looks to me like it follows all the rules regarding promises. Yet running it logs an exception regarding an unhandled promise rejection with the associated console message. (This also happens in FF, Chrome, and Node v10.)

The try/catch block is clearly there and wraps the rejected promise, so what's going on and how would I fix it?

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

  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.log(`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.log(`await finished`, Date.now() - start)
  }
}

example()
like image 423
Ouroborus Avatar asked Jan 28 '23 04:01

Ouroborus


1 Answers

The problem is that, at the point that the rej call rejects, the interpreter hasn't yet gotten to a line that awaits the promise created by rej, so the rejected Promise is just that, a rejected Promise, rather than a Promise that the current thread is 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

So, the behavior is the same as if a rejected Promise is declared without a catch handler. (Errors thrown by Promises will only be caught in an async function if they're awaited at the point at which the Promise rejects - otherwise, it'll just result in an unhandled promise rejection.)

I'd suggest either declaring Promises at the same point that you await them:

const data1 = await res(3000)

(note: the above method's timing won't be the same as the original code)

or use await Promise.all for all Promises, which means that the Promise the interpreter is currently awaiting will throw (and thereby enter the catch block) as soon as one of the Promises rejects:

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.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.log(`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.log(`error caught: await finished`, Date.now() - start)
  }
}

example()

To do additional work while the three Promises are ongoing, and catch errors from those Promises as well as from the main thread, pass a fourth item to the Promise.all, an IIFE that does the additional work you want to do:

async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.log(`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.log('doing work...');
      })()
    ]);
  } catch (error) {
    console.log(`error caught: await finished`, Date.now() - start)
  }
}

example()
like image 199
CertainPerformance Avatar answered Jan 31 '23 22:01

CertainPerformance