JS Promises: Allowing Propagation of Errors JS Promises: Allowing Propagation of Errors express express

JS Promises: Allowing Propagation of Errors


Assuming you have bound your function with something like

app.get('/', controller);

When Express calls controller, it has yielded 100% control to you. If an exception is thrown synchronously from controller, Express is nice and will also treat that as an error for you. As soon as you invoke any asynchronous code however, it is your responsibility to decide how to handle any errors.

In the case of Express, you have two options:

  1. Since you were passed req and res, you can catch an error and send whatever response you want back to the user.
  2. controller actually has a function signature of function(req, res, next) in Express. This is a very common format.

The next callback expects to be called if you have not written anything in the response yet. If you call next() with no arguments, this tells Express to look keep processing the set of URL handlers that it has, trying to find one that will handle the request, returning a 404 if none are found.

If however, you pass an argument to next like next(err), Express will skip over remaining URL handlers, instead looking for error handlers. Express allows you to register custom handlers, but if none are found, it will return a 500.

So what should you do in your example? You probably want something like

function controller (req, res, next) {    find().then(function (result)) {        if (!result) throw new SpecificError("Couldn't find it.");        res.send("It exists!");    }).catch(next);}

That means that if any exception is thrown inside of the promise chain, the next function will be called with it, and Express will take over from there.


Promise handlers are "throw safe". That means any exception you throw in any promise handler will be caught automatically and turned into a rejected promise. That is how the specification is written for promises and how they work (except for some versions of jQuery promises, but that's just because they aren't following the spec).

So, if you are getting "Unhandled rejection" from your promise library, that's designed to be a helpful warning to tell you that you had a rejected promise that had no handler for it so the rejection was silently ignored which is usually a coding mistake.

And, in fact in your controller() function, you have exactly that:

function controller (req, res) {    // ... omitted ...    find().then(function (result)) {        if (result) {            // let 'res' be the Express response object            res.send("It exists!");        } else {            // let SpecificError be a prototypical subclass of Error            throw new SpecificError("Couldn't find it.");        }    }).catch(function (error) {        // throw the error again, so that the error handler can finish        // the job        throw error;    });}

If you get to the line where it says throw new SpecificError, then that will turn the promise into a rejected promise. That will then cause your .catch() handler to get called. In that handler, you throw again which will keep the promise as a rejected promise. So, the original promise that started with find().then(...) will now be a rejected promise. But, there are no more reject handlers and you are not returning the promise from controller(). So, you have an unhandled rejected promise at that point. That is usually a coding mistake.

You have several choices for how to correct this coding mistake:

  1. You can handled the error yourself in the .catch() handler by calling some sort of error handling function that you pass the error to and you pass the res argument to and then don't throw the error.

  2. You can return the promise from your controller() function and Whatever code is calling that function can then handle the rejected promise there.