Home > other >  Why can't try/catch handle error throw in Promise constructor
Why can't try/catch handle error throw in Promise constructor

Time:08-20

This code get's unhandledRejection error and I don't know why.

If the Error is thrown in try/catch, shouldn't it be caught by Catch Expression?

async function main () {
  try {
    await run(throwError)
  } catch (error) {
    console.log('main catch error', error);
  }
}

async function run (callback) {
  return new Promise(async resolve => {
    await throwError()
  });
}

async function throwError () {
  throw new Error('custom error')
}

process.on('unhandledRejection', (reason, promise) => {
  console.log('unhandledRejection - reason', reason, promise);
})

main()

CodePudding user response:

It's not caught because you're passing an async function into new Promise. An error inside an async function rejects the promise that the function returns. The Promise constructor doesn't do anything a promise returned by the function you pass it (the return value of that function is completely ignored), so the rejection goes unhandled. This is one of the promise anti-patterns: Don't provide a promise to something that won't handle it (like addEventListener on the web, or the Promise constructor, or forEach, ...).

Similarly, there's no reason to use new Promise in your async function at all. async functions already return promises. That's another anti-pattern, sometimes called the explicit promise construction anti-pattern. (But see below if you're wrapping an old-fashioned callback API.)

If you remove the unnecessary new Promise, it works as you expect (I also updated run to call callback rather than ignoring it and calling throwError directly):

async function main() {
    try {
        await run(throwError);
    } catch (error) {
        console.log("main catch error", error);
    }
}

async function run(callback) {
    return await callback();
}

async function throwError() {
    throw new Error("custom error");
}

process.on("unhandledRejection", (reason, promise) => {
    console.log("unhandledRejection - reason", reason, promise);
});

main();

About the return await callback(); in that example: Because whatever you return from an async function is used to settle the function's promise (fulfilling it if you return a non-thenable, resolving it to the thenable if you return one), and that return is at the top level of the function (not inside a try/catch or similar), you could just write return callback();. In fact, you could even remove async from run and do that. But keeping the await makes the async stack trace clearer on some JavaScript engines, more clearly indicates what you're doing, and keeps working correctly even if someone comes along later and puts a try/catch around that code.


In a comment, you said that you used new Promise because you were wrapping around a callback-based API. Here's how you'd do that (see also the answers here):

// Assuming `callback` is a Node.js-style callback API, not an
// `async` function as in the question:
function run(callback) {
    return new Promise((resolve, reject) => {
        callback((err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

but, you don't have to do that yourself if the callback-based API uses the standard Node.js convention as above, there's a promisify function in utils.

CodePudding user response:

You don't want to throw an error in this case, you want to invoke reject:

  return new Promise((resolve, reject) => {
    reject('custom error')
  });

If the error being thrown is out of your control, you can catch it inside the promise implementation and reject in that case.

  • Related