How do you prevent promises swallowing errors
Neeraj Kumar
Posted on October 1, 2022
While using asynchronous code, JavaScript’s ES6 promises can make your life a lot easier without having callback pyramids and error handling on every second line. But Promises have some pitfalls and the biggest one is swallowing errors by default.
Let's say you expect to print an error to the console for all the below cases,
Promise.resolve("promised value").then(function () {
throw new Error("error");
});
Promise.reject("error value").catch(function () {
throw new Error("error");
});
new Promise(function (resolve, reject) {
throw new Error("error");
});
But there are many modern JavaScript environments that won't print any errors. You can fix this problem in different ways,
1.Add catch block at the end of each chain
You can add catch block to the end of each of your promise chains.
Promise.resolve("promised value")
.then(function () {
throw new Error("error");
})
.catch(function (error) {
console.error(error.stack);
});
But it is quite difficult to type for each promise chain and verbose too.
2.** Add done method**
You can replace first solution's then and catch blocks with done method.
Promise.resolve("promised value").done(function () {
throw new Error("error");
});
Let's say you want to fetch data using HTTP and later perform processing on the resulting data asynchronously. You can write done block as below,
getDataFromHttp()
.then(function (result) {
return processDataAsync(result);
})
.done(function (processed) {
displayData(processed);
});
In future, if the processing library API changed to synchronous then you can remove done block as below,
getDataFromHttp().then(function (result) {
return displayData(processDataAsync(result));
});
and then you forgot to add done block to then block leads to silent errors.
3.Extend ES6 Promises by Bluebird
Bluebird extends the ES6 Promises API to avoid the issue in the second solution. This library has a “default” onRejection handler which will print all errors from rejected Promises to stderr. After installation, you can process unhandled rejections.
Promise.onPossiblyUnhandledRejection(function (error) {
throw error;
});
and discard a rejection, just handle it with an empty catch.
Promise.reject("error value").catch(function () {});
Stop JavaScript Promises Swallowing Exceptions.
It’s very hard to debug a crash when no stack traces are printed. It becomes a case of manually trying to find the error.
GET /foo/bar/
Doing something useful
Error: Expected } near ;
ES6 promises doesn’t seem to offer the functionality to change this, and bluebird has on[Possibly]UnhandledRejection, which can only be used if you don’t add a .catch() case to the promise. There is no global callback for a rejection unless it’s unhandled. To workaround this, we’re going to need to override the method which runs the callbacks. This is a little hacky, and relies on the library not changing - but it’s better than swallowing errors.
First, if you haven’t already, install Bluebird.
npm install --save bluebird
Next, make a file somewhere (perhaps called bluebird.js) with this as its contents.
const Promise = require('bluebird')
// Throw errors in promises rather than calling reject()
// Makes debugging A LOT easier
Promise.prototype._rejectCallback_old = Promise.prototype._rejectCallback
Promise.prototype._rejectCallback =
function(reason, synchronous, ignoreNonErrorWarnings) {
if (reason.stack) {
throw reasong
} else {
this._rejectCallback_old(reason, synchronous, ignoreNonErrorWarnings)
}
}
module.exports = Promise
Alternatively you could just print reason.stack if it exists, however I prefer a full crash whilst debugging.
Posted on October 1, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.