Errors thrown from promises are handled by the second parameter (reject
) passed to then
or by the handler passed to catch
:
throwErrorAsync()
.then(null, error => { /* handle error here */ });
// or
throwErrorAsync()
.catch(error => { /* handle error here */ });
If you have a promise chain then an error will cause resolve
handlers to be skipped:
throwErrorAsync()
.then(() => { /* never called */ })
.catch(error => { /* handle error here */ });
The same applies to your then
functions. If a resolve
handler throws an exception then the next reject
handler will be invoked:
doSomethingAsync()
.then(result => { throwErrorSync(); })
.then(() => { /* never called */ })
.catch(error => { /* handle error from throwErrorSync() */ });
An error handler returns a new promise, allowing you to continue a promise chain. The promise returned by the error handler is resolved with the value returned by the handler:
throwErrorAsync()
.catch(error => { /* handle error here */; return result; })
.then(result => { /* handle result here */ });
You can let an error cascade down a promise chain by re-throwing the error:
throwErrorAsync()
.catch(error => {
/* handle error from throwErrorAsync() */
throw error;
})
.then(() => { /* will not be called if there's an error */ })
.catch(error => { /* will get called with the same error */ });
It is possible to throw an exception that is not handled by the promise by wrapping the throw
statement inside a setTimeout
callback:
new Promise((resolve, reject) => {
setTimeout(() => { throw new Error(); });
});
This works because promises cannot handle exceptions thrown asynchronously.
An error will be silently ignored if a promise doesn't have a catch
block or reject
handler:
throwErrorAsync()
.then(() => { /* will not be called */ });
// error silently ignored
To prevent this, always use a catch
block:
throwErrorAsync()
.then(() => { /* will not be called */ })
.catch(error => { /* handle error*/ });
// or
throwErrorAsync()
.then(() => { /* will not be called */ }, error => { /* handle error*/ });
Alternatively, subscribe to the unhandledrejection
event to catch any unhandled rejected promises:
window.addEventListener('unhandledrejection', event => {});
Some promises can handle their rejection later than their creation time. The rejectionhandled
event gets fired whenever such a promise is handled:
window.addEventListener('unhandledrejection', event => console.log('unhandled'));
window.addEventListener('rejectionhandled', event => console.log('handled'));
var p = Promise.reject('test');
setTimeout(() => p.catch(console.log), 1000);
// Will print 'unhandled', and after one second 'test' and 'handled'
The event
argument contains information about the rejection. event.reason
is the error object and event.promise
is the promise object that caused the event.
In Nodejs the rejectionhandled
and unhandledrejection
events are called rejectionHandled
and unhandledRejection
on process
, respectively, and have a different signature:
process.on('rejectionHandled', (reason, promise) => {});
process.on('unhandledRejection', (reason, promise) => {});
The reason
argument is the error object and the promise
argument is a reference to the promise object that caused the event to fire.
Usage of these unhandledrejection
and rejectionhandled
events should be considered for debugging purposes only. Typically, all promises should handle their rejections.
Note: Currently, only Chrome 49+ and Node.js support unhandledrejection
and rejectionhandled
events.
fulfill
and reject
The then(fulfill, reject)
function (with both parameters not null
) has unique and complex behavior, and shouldn't be used unless you know exactly how it works.
The function works as expected if given null
for one of the inputs:
// the following calls are equivalent
promise.then(fulfill, null)
promise.then(fulfill)
// the following calls are also equivalent
promise.then(null, reject)
promise.catch(reject)
However, it adopts unique behavior when both inputs are given:
// the following calls are not equivalent!
promise.then(fulfill, reject)
promise.then(fulfill).catch(reject)
// the following calls are not equivalent!
promise.then(fulfill, reject)
promise.catch(reject).then(fulfill)
The then(fulfill, reject)
function looks like it is a shortcut for then(fulfill).catch(reject)
, but it is not, and will cause problems if used interchangeably. One such problem is that the reject
handler does not handle errors from the fulfill
handler. Here is what will happen:
Promise.resolve() // previous promise is fulfilled
.then(() => { throw new Error(); }, // error in the fulfill handler
error => { /* this is not called! */ });
The above code will result in a rejected promise because the error is propagated. Compare it to the following code, which results in a fulfilled promise:
Promise.resolve() // previous promise is fulfilled
.then(() => { throw new Error(); }) // error in the fulfill handler
.catch(error => { /* handle error */ });
A similar problem exists when using then(fulfill, reject)
interchangeably with catch(reject).then(fulfill)
, except with propagating fulfilled promises instead of rejected promises.
Imagine a function like this:
function foo(arg) {
if (arg === 'unexepectedValue') {
throw new Error('UnexpectedValue')
}
return new Promise(resolve =>
setTimeout(() => resolve(arg), 1000)
)
}
If such function is used in the middle of a promise chain, then apparently there is no problem:
makeSomethingAsync().
.then(() => foo('unexpectedValue'))
.catch(err => console.log(err)) // <-- Error: UnexpectedValue will be caught here
However, if the same function is called outside of a promise chain, then the error will not be handled by it and will be thrown to the application:
foo('unexpectedValue') // <-- error will be thrown, so the application will crash
.then(makeSomethingAsync) // <-- will not run
.catch(err => console.log(err)) // <-- will not catch
There are 2 possible workarounds:
Instead of throwing, do as follows:
function foo(arg) {
if (arg === 'unexepectedValue') {
return Promise.reject(new Error('UnexpectedValue'))
}
return new Promise(resolve =>
setTimeout(() => resolve(arg), 1000)
)
}
Your throw
statement will be properly caught when it is already inside a promise chain:
function foo(arg) {
return Promise.resolve()
.then(() => {
if (arg === 'unexepectedValue') {
throw new Error('UnexpectedValue')
}
return new Promise(resolve =>
setTimeout(() => resolve(arg), 1000)
)
})
}