In node.js, is there a good explanation of how exceptions work in conjunction with 'rejection' of promises?
2 Answers
Promise rejection is the asynchronous analogue to exceptions in synchronous code. An exception thrown inside a promise handler (be it in then()
, catch()
or finally()
) is automatically converted to a promise rejection.
However, the opposite is not true. If you create a promise, or call a function that returns a promise that will ultimately be rejected, within a try/catch
block there is no automatic conversion to an exception. But you can do it using async/await
. This will work:
async function() {
try {
await getPromiseThatWillBeRejected();
}
catch (e) {
// error that caused rejection can be handled here
}
}
The await
keyword virtually converts this to a synchronous blocking call (it is not really blocking but subsequent code is only executed after the promise is fulfilled or rejected) and converts the rejection to a synchronous exception.

- 11,173
- 2
- 44
- 69
The following is a walkthrough of the combinations of exceptions and promise rejections.
Let's start with a promise that throws an exception, and another that completes the promise with a rejection, to compare them.
function thrower() {
return new Promise((resolve, reject) => { throw new Error('thrown'); });
}
function rejecter() {
return new Promise((resolve, reject) => reject('rejected'));
}
The following calling code will receive the error in the same way:
thrower.catch(ex => console.log('caught ' + ex));
rejecter.catch(ex => console.log('caught ' + ex));
The result is caught Error: thrown
and caught rejected
. An exception reaching the catch
function is straight forward. But it is strange that a rejected promise ends up in a catch
function too. That's how it is, let's move on.
Similarly, an exception becomes a rejection. The following uses the second parameter of then
to provide an onrejection
function:
thrower().then(() => {}, err => console.log('err ' + err));
rejecter().then(() => {}, err => console.log('err ' + err));
Result is err Error: thrown
and err rejected
. Again, this makes sense, rejections reach the onrejection
handler. It's a stretch, but meh sure, that exceptions land there too.
Now what if both onrejection
handler and catch
are used?
thrower().then(() => {}, err => console.log('err ' + err)).catch(ex => console.log('caught ' + ex));
rejecter().then(() => {}, err => console.log('err ' + err)).catch(ex => console.log('caught ' + ex));
The outcome is err Error: thrown
and err rejected
. Here it is somewhat confusing. The language routes the exception into the err
function, and not to the catch
function.
An important point, this code does not catch the exception:
try {
thrower();
}
catch(ex) {
console.log('caught ' + ex); // won't catch thrower's exception
}
Why of course - the try/catch
block execution completes before the promise is executed.
But it is less obvious that this doesn't work either:
function throwerThrower() {
return new Promise((resolve, reject) => { thrower().catch(ex => { throw ex }); });
}
throwerThrower().catch(ex => console.log('caught ' + ex)); // not going to catch nested throw
Cascading an exception like above will result in an unhandled exception.
However this next block does work and maybe shows how valuable it is to reject instead of throw:
function throwerRejecter() {
return new Promise((resolve, reject) => { thrower().catch(ex => reject(ex)); });
}
throwerRejecter().catch(ex => console.log('caught ' + ex));
Finally, here are the last two nested combinations:
function rejecterThrower() {
return new Promise((resolve, reject) => { rejecter().catch(ex => { throw ex }); });
}
rejecterThrower().catch(ex => console.log('caught ' + ex));
Returns ERR_UNHANDLED_REJECTION
and that looks sensible as rejection handling threw an exception.
And
function rejecterRejecter() {
return new Promise((resolve, reject) => { rejecter().catch(ex => reject(ex)); });
}
rejecterRejecter().catch(ex => console.log('caught ' + ex));
Prints caught rejected
- a simple cascade of the rejection that is obvious.

- 2,171
- 19
- 30
-
1You write "The language routes the exception into the err function, and not to the catch function." This is not really confusing if you know that the error handler (second parameter to `then`) is basically the same as the parameter passed to `catch()`. The exception/rejection is already caught before `catch()` is encountered. – lex82 Dec 21 '20 at 10:05
-
@lex82 is absolutely right about the second function value in the `then` function being the `catch` handler function – orimdominic Feb 05 '23 at 07:39