Handling multiple catches in promise chain

This behavior is exactly like a synchronous throw:

try{
    throw new Error();
} catch(e){
    // handle
} 
// this code will run, since you recovered from the error!

That’s half of the point of .catch – to be able to recover from errors. It might be desirable to rethrow to signal the state is still an error:

try{
    throw new Error();
} catch(e){
    // handle
    throw e; // or a wrapper over e so we know it wasn't handled
} 
// this code will not run

However, this alone won’t work in your case since the error be caught by a later handler. The real issue here is that generalized “HANDLE ANYTHING” error handlers are a bad practice in general and are extremely frowned upon in other programming languages and ecosystems. For this reason Bluebird offers typed and predicate catches.

The added advantage is that your business logic does not (and shouldn’t) have to be aware of the request/response cycle at all. It is not the query’s responsibility to decide which HTTP status and error the client gets and later as your app grows you might want to separate the business logic (how to query your DB and how to process your data) from what you send to the client (what http status code, what text and what response).

Here is how I’d write your code.

First, I’d get .Query to throw a NoSuchAccountError, I’d subclass it from Promise.OperationalError which Bluebird already provides. If you’re unsure how to subclass an error let me know.

I’d additionally subclass it for AuthenticationError and then do something like:

function changePassword(queryDataEtc){ 
    return repository.Query(getAccountByIdQuery)
                     .then(convertDocumentToModel)
                     .then(verifyOldPassword)
                     .then(changePassword);
}

As you can see – it’s very clean and you can read the text like an instruction manual of what happens in the process. It is also separated from the request/response.

Now, I’d call it from the route handler as such:

 changePassword(params)
 .catch(NoSuchAccountError, function(e){
     res.status(404).send({ error: "No account found with this Id" });
 }).catch(AuthenticationError, function(e){
     res.status(406).send({ OldPassword: error });
 }).error(function(e){ // catches any remaining operational errors
     res.status(500).send({ error: "Unable to change password" });
 }).catch(function(e){
     res.status(500).send({ error: "Unknown internal server error" });
 });

This way, the logic is all in one place and the decision of how to handle errors to the client is all in one place and they don’t clutter eachother.

Leave a Comment