I'm writing a multi-step async sequence that involves different possible errors at each step. I want to abort the whole sequence if an error occurs.
I am trying to express this as a linear sequence using Promise.then() and Promise.catch(). Here is the pseudocode:
step1()
.catch(errorAtStep1 => handlerErrorAtStep1)
.then(resultOfStep1 => step2(resultOfStep1))
.catch(errorAtStep2 => handlerErrorAtStep2)
.then(resultOfStep2 => step3(resultOfStep2))
...
I understand that I need to throw something in each error handler to avoid getting back into the then(result) parts. When I throw, though, the execution ends up in the next catch(error) block, which was expecting an error that's related to its corresponding step, not that other thing that I threw.
My question is: what are clean ways to handle this, that would minimize distraction when reading the code?
I came up with this class:
class AbortChainError extends Error {
static chain(handler) {
return function(error) {
if (error instanceof AbortChainError) throw error
handler(error)
throw new AbortChainError()
}
}
}
that allows the catch() blocks to be written as:
.catch(AbortChainError.chain(errorAtStep2 => handlerErrorAtStep2))
with the additional need to write a final, empty catch() block at the end of the sequence.
Can we do better?
If you put this into an async function, I'd have the error handlers return nothing, and then you can do each step one-by-one and see if the result exists or not - if it doesn't, there was an error, and you can return.
const doStuff = async () => {
const r1 = await step1().catch(handlerErrorAtStep1);
if (!r1) return;
const r2 = await step2(r1).catch(handlerErrorAtStep2);
if (!r2) return;
// etc
It's not the most DRY, but I think it's the easiest to understand.