Handling multiple catches in promise chain

Javascriptnode.jsPromiseBluebird

Javascript Problem Overview


I am still fairly new to promises and am using bluebird currently, however I have a scenario where I am not quite sure how to best deal with it.

So for example I have a promise chain within an express app like so:

repository.Query(getAccountByIdQuery)
		.catch(function(error){
			res.status(404).send({ error: "No account found with this Id" });
		})
		.then(convertDocumentToModel)
		.then(verifyOldPassword)
		.catch(function(error) {
			res.status(406).send({ OldPassword: error });
		})
		.then(changePassword)
		.then(function(){
			res.status(200).send();
		})
		.catch(function(error){
			console.log(error);
			res.status(500).send({ error: "Unable to change password" });
		});

So the behaviour I am after is:

  • Goes to get account by Id
  • If there is a rejection at this point, bomb out and return an error
  • If there is no error convert the document returned to a model
  • Verify the password with the database document
  • If the passwords dont match then bomb out and return a different error
  • If there is no error change the passwords
  • Then return success
  • If anything else went wrong, return a 500

So currently catches do not seem to stop the chaining, and that makes sense, so I am wondering if there is a way for me to somehow force the chain to stop at a certain point based upon the errors, or if there is a better way to structure this to get some form of branching behaviour, as there is a case of if X do Y else Z.

Any help would be great.

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

.catch works like the try-catch statement, which means you only need one catch at the end:

repository.Query(getAccountByIdQuery)
        .then(convertDocumentToModel)
        .then(verifyOldPassword)
        .then(changePassword)
        .then(function(){
            res.status(200).send();
        })
        .catch(function(error) {
            if (/*see if error is not found error*/) {
                res.status(404).send({ error: "No account found with this Id" });
            } else if (/*see if error is verification error*/) {
                res.status(406).send({ OldPassword: error });
            } else {
                console.log(error);
                res.status(500).send({ error: "Unable to change password" });
            }
        });

Solution 3 - Javascript

> I am wondering if there is a way for me to somehow force the chain to stop at a certain point based upon the errors

No. You cannot really "end" a chain, unless you throw an exception that bubbles until its end. See Benjamin Gruenbaum's answer for how to do that.

A derivation of his pattern would be not to distinguish error types, but use errors that have statusCode and body fields which can be sent from a single, generic .catch handler. Depending on your application structure, his solution might be cleaner though.

> or if there is a better way to structure this to get some form of branching behaviour

Yes, you can do branching with promises. However, this means to leave the chain and "go back" to nesting - just like you'd do in an nested if-else or try-catch statement:

repository.Query(getAccountByIdQuery)
.then(function(account) {
    return convertDocumentToModel(account)
    .then(verifyOldPassword)
    .then(function(verification) {
        return changePassword(verification)
        .then(function() {
            res.status(200).send();
        })
    }, function(verificationError) {
        res.status(406).send({ OldPassword: error });
    })
}, function(accountError){
    res.status(404).send({ error: "No account found with this Id" });
})
.catch(function(error){
    console.log(error);
    res.status(500).send({ error: "Unable to change password" });
});

Solution 4 - Javascript

I have been doing this way:

You leave your catch in the end. And just throw an error when it happens midway your chain.

    repository.Query(getAccountByIdQuery)
    .then((resultOfQuery) => convertDocumentToModel(resultOfQuery)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')
    .then((model) => verifyOldPassword(model)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')        
    .then(changePassword)
    .then(function(){
        res.status(200).send();
    })
    .catch((error) => {
    if (error.name === 'no_account'){
        res.status(404).send({ error: "No account found with this Id" });
       
    } else  if (error.name === 'wrong_old_password'){
        res.status(406).send({ OldPassword: error });
       
    } else {
         res.status(500).send({ error: "Unable to change password" });
       
    }
});

Your other functions would probably look something like this:

function convertDocumentToModel(resultOfQuery) {
    if (!resultOfQuery){
        throw new Error('no_account');
    } else {
    return new Promise(function(resolve) {
        //do stuff then resolve
        resolve(model);
    }                       
}

Solution 5 - Javascript

Probably a little late to the party, but it is possible to nest .catch as shown here:

Mozilla Developer Network - Using Promises

Edit: I submitted this because it provides the asked functionality in general. However it doesn't in this particular case. Because as explained in detail by others already, .catch is supposed to recover the error. You can't, for example, send a response to the client in multiple .catch callbacks because a .catch with no explicit return resolves it with undefined in that case, causing proceeding .then to trigger even though your chain is not really resolved, potentially causing a following .catch to trigger and sending another response to the client, causing an error and likely throwing an UnhandledPromiseRejection your way. I hope this convoluted sentence made some sense to you.

Solution 6 - Javascript

Instead of .then().catch()... you can do .then(resolveFunc, rejectFunc). This promise chain would be better if you handled things along the way. Here is how I would rewrite it:

repository.Query(getAccountByIdQuery)
    .then(
        convertDocumentToModel,
        () => {
            res.status(404).send({ error: "No account found with this Id" });
            return Promise.reject(null)
        }
    )
    .then(
        verifyOldPassword,
        () => Promise.reject(null)
    )
    .then(
        changePassword,
        (error) => {
            if (error != null) {
                res.status(406).send({ OldPassword: error });
            }
            return Promise.Promise.reject(null);
        }
    )
    .then(
        _ => res.status(200).send(),
        error => {
            if (error != null) {
                console.error(error);
                res.status(500).send({ error: "Unable to change password" });
            }
        }
    );

Note: The if (error != null) is a bit of a hack to interact with the most recent error.

Solution 7 - Javascript

I think Benjamin Gruenbaum's answer above is the best solution for a complex logic sequence, but here is my alternative for simpler situations. I just use an errorEncountered flag along with return Promise.reject() to skip any subsequent then or catch statements. So it would look like this:

let errorEncountered = false;
someCall({
  /* do stuff */
})
.catch({
  /* handle error from someCall*/
  errorEncountered = true;
  return Promise.reject();
})
.then({
  /* do other stuff */
  /* this is skipped if the preceding catch was triggered, due to Promise.reject */
})
.catch({
  if (errorEncountered) {
    return;
  }
  /* handle error from preceding then, if it was executed */
  /* if the preceding catch was executed, this is skipped due to the errorEncountered flag */
});

If you have more than two then/catch pairs, you should probably use Benjamin Gruenbaum's solution. But this works for a simple set-up.

Note that the final catch only has return; rather than return Promise.reject();, because there's no subsequent then that we need to skip, and it would count as an unhandled Promise rejection, which Node doesn't like. As is written above, the final catch will return a peacefully resolved Promise.

Solution 8 - Javascript

I wanted to preserve the branching behaviour that Bergi's answer had, yet still provide the clean code structure of unnested .then()'s

If you can handle some ugliness in the machinery that makes this code work, the result is a clean code structure similar to non-nested chained .then()'s

One nice part of structuring a chain like this, is that you can handle all the potential results in one place by chainRequests(...).then(handleAllPotentialResults) this might be nice if you need to hide the request chain behind some standardised interface.

const log = console.log;
const chainRequest = (stepFunction, step) => (response) => {
    if (response.status === 200) {
        return stepFunction(response, step);
    }
    else {
        log(`Failure at step: ${step}`);
        return response;
    }
};
const chainRequests = (initialRequest, ...steps) => {
    const recurs = (step) => (response) => {
        const incStep = step + 1;
        const nextStep = steps.shift();
        return nextStep ? nextStep(response, step).then(chainRequest(recurs(incStep), incStep)) : response;
    };
    return initialRequest().then(recurs(0));
};
// Usage 
async function workingExample() {
    return await chainRequests(
        () => fetch('https://jsonplaceholder.typicode.com/users'), 
        (resp, step) => { log(`step: ${step}`, resp); return fetch('https://jsonplaceholder.typicode.com/posts/'); },
        (resp, step) => { log(`step: ${step}`, resp); return fetch('https://jsonplaceholder.typicode.com/posts/3'); }
    );
}
async function failureExample() {
    return await chainRequests(
        () => fetch('https://jsonplaceholder.typicode.com/users'),
        (resp, step) => { log(`step: ${step}`, resp); return fetch('https://jsonplaceholder.typicode.com/posts/fail'); },
        (resp, step) => { log(`step: ${step}`, resp); return fetch('https://jsonplaceholder.typicode.com/posts/3'); }
    );
}
console.log(await workingExample());
console.log(await failureExample());

The idea is there, but the interface exposed could probably use some tweaking.

Seeing as this implementation used curried arrow functions, the above could potentially be implemented with more direct async/await code

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGrofitView Question on Stackoverflow
Solution 1 - JavascriptBenjamin GruenbaumView Answer on Stackoverflow
Solution 2 - JavascriptEsailijaView Answer on Stackoverflow
Solution 3 - JavascriptBergiView Answer on Stackoverflow
Solution 4 - JavascriptLeo LeaoView Answer on Stackoverflow
Solution 5 - JavascripttltView Answer on Stackoverflow
Solution 6 - JavascriptmvndaaiView Answer on Stackoverflow
Solution 7 - Javascripttemporary_user_nameView Answer on Stackoverflow
Solution 8 - JavascriptJames SchinnerView Answer on Stackoverflow