Handling Multiple Catches in Promise Chain

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.

try catch vs promise chaining mechanism for error handing which one offers more flexibility?

Firstly, try... catch is useful to catch mostly expected errors (when you need to return some 4xxx error with additional info or even 200 with a business logic error code or something like that) rather than some unexpected errors that should lead to 500 HTTP errors. That said you need a global handler for all non-catched/unexpected errors that will log these errors and send the 500 error to a client.

global error handler

  app.use(function (err, req, res, next) {
logger.error(err)

res.status(500).send('Unexpected error')
})

try/catch with an explicit transaction

const registerNewUser = async (req, res) => {
/*
code here for extracting info from req
*/
// here is some call to create a transaction in a DB
// it varies depending on a type of DB and a package you use to communicate with it
// we need to create a transaction BEFORE try/catch block
const transaction = await createTransaction();
try {
// we need to pass transaction to every method that modifies data in DB
// also depends on how to pass a transaction to underlying DB package methods
const users_data = await users.create_new_user({
email,
crypted_pwd,
salt,
first_name,
phone_code,
}, transaction);

const { id: user_id, first_name: user_first_name } = users_data;

// we need to pass transaction to every method that modifies data in DB
const customer_data = await customers.create_new_customer({
first_name,
last_name,
email,
}, transaction);

const {
tenant_id,
id: customer_id,
} = customer_data;

await clearCartFromRedis(merchant_canonical_name, req.token);

signUpWelcomeMailer(merchant_canonical_name, req.token, user_id);

// here we need to commit the transaction before we exit from this handler
await transaction.commit();

return res.status(200).json({
success: true,
access_token: req.token,
first_name: user_first_name,
});
} catch (error) {
// here we need to rollback the transaction before we exit from this handler
await transaction.rollback();
logger.log({ message: error.message, level: 'error' });
return res.status(500).send(error.message);
}
};

Secondly, in your case of several operations against some DB you clearly need to use a transaction mechanism to keep data consistent. Usually, it requires creating an explicit transaction and passing it to each query that should be executed as a one atomic operation that either executes correctly (then you need to commit a transaction) or fails (then you need to rollback a transaction). For that, you can use try...catch to be able to commit or rollback a transaction at the end of a batch of operations.

Why catch at the end of promise chain is not catching all the errors?

Looks like the issue is that isUsed is being executed immediately rather than after inBlacklist resolves, so your .then() is being given a Promise object rather than a callback function. It's just like if you wrote a(b(c));, b is executed first and then the return value is passed to a.

Instead, you want to pass to .then a callback function that executes the next function, so try using arrow functions inside the thens, like so:

inBlacklist(address)
.then(() => isUsed(payments))
.then(() => isProcessed(ref_num))
.then(() => validateType(type))
.catch((err) => {
console.log(err);
return res.status(400).send({ message: err.message });
});

Can I have multiple .finally() as well as multiple .catch() in Promise.all()?

Sure, finally is a chainable promise method just like catch (with the only difference that its callback does not take a parameter). You can use it as many times as you want, on any promise.

Promise.all([
p1.finally(() => { console.log("p1 completed"); }),
p2.finally(() => { console.log("p2 completed"); }),
]).finally(() => {
console.log("all() completed");
})

JS Promises.all() - how to prevent a 'catch' from stopping the promise chain?

Yes, all you need to do is return something from the catch of the failed promise and it will be "recovered" as a resolve in the Promise.all

Hopefully this demonstrates. Without the catch in somePromisyFunction2 the whole chain will fail (check by commenting it out) but with it the whole chain succeeds with the recovered value

let promise_array = [];

let p = somePromisyFunction1()
//.then(() => { // Do something good })
// .catch(() => { // Handle the bad });
promise_array.push(p);

p = somePromisyFunction2()
//.then(() => { // Do something good })
.catch((v) => "2 - " + v);
promise_array.push(p);

p = somePromisyFunction3()
//.then(() => { // Do something good })
//.catch(() => { // Handle the bad });
promise_array.push(p);

// ...
// More Promisy functions added to promise_array here...
// ...

Promise.all(promise_array)
.then((v) => { console.log("all good",v) })
.catch(() => { console.log("something bad") });


function somePromisyFunction1(){
return new Promise(resolve => resolve("1"));
}

function somePromisyFunction2(){
return new Promise((resolve,reject) => reject("oh no something went wrong!"));
}

function somePromisyFunction3(){
return new Promise(resolve => resolve("3"));
}

conditional promise chaining multiple levels

Promise chains are not elegant for this. You have to specify the whole flow upfront:

promise1(param1)
.then(resp1 => {
if(resp1 == something) {
...
console.log(resp1) // I want to end here if condition is met
}
else {
...
return promise2.then(resp2 => {
....
return promise3
})
.then(resp3 => {
....
console.log(resp3) // else end here
})
}
})
.catch(err => {
console.log(err)
});

If you can use async/await, it will be much more elegant

async function doSomething(){
const resp1 = await promise1(param1);
if(resp1 == something) {
return resp1;
}

const resp2 = await createPromise2(resp1);
const resp3 = await createPromise3(resp2);
return resp3;
}


Related Topics



Leave a reply



Submit