DEV Community

Cover image for How do you prevent promises swallowing errors
Neeraj Kumar
Neeraj Kumar

Posted on

How do you prevent promises swallowing errors

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");
});
Enter fullscreen mode Exit fullscreen mode

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);
  });
Enter fullscreen mode Exit fullscreen mode

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");
});
Enter fullscreen mode Exit fullscreen mode

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);
  });
Enter fullscreen mode Exit fullscreen mode

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));
});
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

and discard a rejection, just handle it with an empty catch.

Promise.reject("error value").catch(function () {});
Enter fullscreen mode Exit fullscreen mode

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 ;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Alternatively you could just print reason.stack if it exists, however I prefer a full crash whilst debugging.

Top comments (0)