Home > OS >  Properly handling exceptions from async functions at the top level
Properly handling exceptions from async functions at the top level

Time:04-05

I'm having trouble understanding the correct way to handle exceptions from async functions without triggering Node's UnhandledPromiseRejectionWarning (since that seems to suggest that I'm doing something wrong).

Normally, in non-async code, unhandled exceptions bubble all the way to the top and eventually get printed to the console. The code that resulted in the exception is stopped all the way back up the stack.

Say I have the following code:

test1().catch((e) => { throw e; });
console.log('got here top-level'); // This will print

async function test1() {
    let test2Result = await test2();
    console.log('got here test 1'); // This won't print
    return test2Result;
}

async function test2() {
    throw new Error('something failed here');
}

I would expect that this would do what I want. At the top-most level, it catches the exception from the async functions and re-throws (but now outside any async code or Promises). It should still have the original stack trace so I can find the problem. Happy days, right? But no, this still results in UnhandledPromiseRejectionWarning and I don't understand why.

What's the proper way to catch all unhandled exceptions from inside Promises?

CodePudding user response:

Your misunderstanding comes from this statement...

catches the exception from the async functions and re-throws (but now outside any async code or Promises)

Promise.prototype.catch() is still async code. It returns a Promise, just like .then() (in fact, .catch(onRejected) is simply an alias for .then(undefined, onRejected)).

Fun fact, if the .catch() callback doesn't return a rejected promise (eg Promise.reject()) or throw, it returns a successful / resolved promise.

When you throw e within your .catch(), the return value is a rejected Promise and since this is unhandled / uncaught, you get the infamous UnhandledPromiseRejectionWarning.

To avoid it, simply handle all possible promise rejections

test1().catch(console.error); // returns a resolved promise

// or

try {
  await test1();
} catch (e) {
  // consider this handled
}

CodePudding user response:

I generally do something like this:

async function main() {
  // do some async stuff here
  await someAsyncWork();
}

let cc;

main()
.then( () => {
  console.log('success!');
  cc = 0 ;
}
.catch( e => {
  console.error(e.stack);
  cc = 1;
})
.finally( () => process.exit(cc) );
  • Related