Ever since jQuery introduced deferreds, developers have used promises to manage asynchronous control flow. (I know, I know, promises pre-date jQuery, but that’s when they went mainstream). In the years since their introduction promises have been standardized] and are now being implemented natively in browsers.
Before we can look at error handling in promises, it is useful to see a simple example.
function doSomethingAsync(){
return new Promise(function (resolve, reject){
setTimeout(function (){
resolve("success"); // Fulfill the promise successfully
}, 100);
});
}
doSomethingAsync().then(function (fulfilledResult){
console.log(fulfilledResult); // Prints "success"
});
This is a contrived demonstration, but the basic flow is covered here. In this case, we return a promise from the doSomethingAsync
function. The promise returned is “thenable”, meaning it has a then()
function. The callback we pass to then()
is executed once the promise is fulfilled.
What if Something Breaks?
All kinds of things can go wrong in the real world, and the network is never reliable. If an error condition arises inside a promise, you “reject” the promise by calling the reject()
function with an error. To handle a promise rejection, you pass a callback to the catch()
function.
Let’s add some basic error handling to our code:
function doSomethingAsync(){
return new Promise(function (resolve, reject){
setTimeout(function (){
// Note that we reject with an Error object
// This is useful for getting an accurate stack trace!
reject(new Error("something went terribly wrong!"));
}, 100);
});
}
doSomethingAsync()
.then(function (fulfilledResult){
// Never executes because the promise is rejected
console.log(fulfilledResult);
})
.catch(function (err){
console.log(err); // Prints "Error: something went terribly wrong"
});
This is a simple example, so catching the rejection is trivial. As promise chains grow larger, it gets more challenging to reason about the error flow. Additionally, every promise has the potential to fail, so you have two branches you must conceivably handle for every promise.
Promise error handling code can be confusing and clutter the main application logic, so many times we see people elide the catch()
handler altogether. Things work correctly most of the time, right? Ignoring the catch handler might make things easier to read, but what happens if you don’t catch()
a promise rejection?
Unhandled Rejections
When a promise is rejected, it looks for a rejection handler. If it finds one, like in the example above, it calls the function with the error. Perfect. If there’s no rejection handler, the promise throws up its hands and produces a global unhandled rejection error.

The problem here is that, until very recently, there was no good way to capture these errors. Unhandled rejections are not sent through normal console.error()
, nor are they passed to the traditional window.onerror
handler. How can we track them?
Modern Standards to the Rescue
The WHATWG html living standard has now been updated with two new window events to help with promise rejection handling. One is for listening to unhandled promise rejections, the other for listening to handled rejections.
WindowEventHandlers.onunhandledrejection
WindowEventHandlers.onrejectionhandled
Each event handler is passed a PromiseRejectionEvent which contains a PromiseRejectionEvent.reason
property and a PromiseRejectionEvent.promise
property. The former is the actual object the promise was rejected with, and the latter is the promise itself.
Putting it Together With TrackJS
TrackJS automatically listens and catches unhandled promise rejections, so there’s nothing you have to do! If you’d like help dealing with your unhandled promises, try TrackJS today, free for 14 days, and we’ll help you know when these bugs impact your visitors.