How to Break Promise Chain

Break promise chain and call a function based on the step in the chain where it is broken (rejected)

The reason your code doesn't work as expected is that it's actually doing something different from what you think it does.

Let's say you have something like the following:

stepOne()
.then(stepTwo, handleErrorOne)
.then(stepThree, handleErrorTwo)
.then(null, handleErrorThree);

To better understand what's happening, let's pretend this is synchronous code with try/catch blocks:

try {
try {
try {
var a = stepOne();
} catch(e1) {
a = handleErrorOne(e1);
}
var b = stepTwo(a);
} catch(e2) {
b = handleErrorTwo(e2);
}
var c = stepThree(b);
} catch(e3) {
c = handleErrorThree(e3);
}

The onRejected handler (the second argument of then) is essentially an error correction mechanism (like a catch block). If an error is thrown in handleErrorOne, it will be caught by the next catch block (catch(e2)), and so on.

This is obviously not what you intended.

Let's say we want the entire resolution chain to fail no matter what goes wrong:

stepOne()
.then(function(a) {
return stepTwo(a).then(null, handleErrorTwo);
}, handleErrorOne)
.then(function(b) {
return stepThree(b).then(null, handleErrorThree);
});

Note: We can leave the handleErrorOne where it is, because it will only be invoked if stepOne rejects (it's the first function in the chain, so we know that if the chain is rejected at this point, it can only be because of that function's promise).

The important change is that the error handlers for the other functions are not part of the main promise chain. Instead, each step has its own "sub-chain" with an onRejected that is only called if the step was rejected (but can not be reached by the main chain directly).

The reason this works is that both onFulfilled and onRejected are optional arguments to the then method. If a promise is fulfilled (i.e. resolved) and the next then in the chain doesn't have an onFulfilled handler, the chain will continue until there is one with such a handler.

This means the following two lines are equivalent:

stepOne().then(stepTwo, handleErrorOne)
stepOne().then(null, handleErrorOne).then(stepTwo)

But the following line is not equivalent to the two above:

stepOne().then(stepTwo).then(null, handleErrorOne)

Angular's promise library $q is based on kriskowal's Q library (which has a richer API, but contains everything you can find in $q). Q's API docs on GitHub could prove useful. Q implements the Promises/A+ spec, which goes into detail on how then and the promise resolution behaviour works exactly.

EDIT:

Also keep in mind that if you want to break out of the chain in your error handler, it needs to return a rejected promise or throw an Error (which will be caught and wrapped in a rejected promise automatically). If you don't return a promise, then wraps the return value in a resolve promise for you.

This means that if you don't return anything, you are effectively returning a resolved promise for the value undefined.

How do I break a promise chain?

You can throw an Error in the else block, then catch it at the end of the promise chain:

var p = new Promise((resolve, reject) => {
setTimeout(function() {
resolve(1)
}, 0);
});

p
.then((res) => {
if(false) {
return res + 2
} else {
// do something and break the chain here ???
throw new Error('error');
}
})
.then((res) => {
// executed only when the condition is true
console.log(res)
})
.catch(error => {
console.log(error.message);
})

Demo - https://jsbin.com/ludoxifobe/edit?js,console

Breaking a promise chain

Another option is to throw a custom error that can be caught in a singular catch block at the very end like so:

const errorHandler = require('path/to/handler')
class ProcessingError1 extends Error {
constructor(message) {
super(message);
this.name = "ProcessingError1";
}
}
class ProcessingError2 extends Error {
constructor(message) {
this.message = message;
this.name = "ProcessingError2";
}
}
const process1 = async () => {
throw new ProcessingError1("Somethign went wrong");
};
const process2 = async () => {
return { some: "process" };
};
const someApiCall = async () => ({ some: "response" });

someApiCall()
.then(process1)
.then(process2) // process2 is not run if process1 throws an error
.catch(errorHandler);
// ErrorHandler.js

module.exports = e => {
if (e instanceof ProcessingError1) {
// handle error thrown from process1
}
else if (e instanceof ProcessingError2) {
// handle error thrown from process2
}
else {
// handle other errors as needed..
}
}

how to break promise chain

I think you don't want a chain here. In a synchronous fashion, you'd have written

function getMode(){
if (checkIf('A')) {
return 'A';
} else {
if (checkIf('B')) {
return 'B';
} else {
if (checkIf('C')) {
return 'C';
} else {
throw new Error();
}
}
}
}

and this is how it should be translated to promises:

function getMode(){
checkIf('A').then(function(bool) {
if (bool)
return 'A';
return checkIf('B').then(function(bool) {
if (bool)
return 'B';
return checkIf('C').then(function(bool) {
if (bool)
return 'C';
throw new Error();
});
});
});
}

There is no if else-flattening in promises.

Break javascript promise chain in a clean way

In my understanding, step 2 should break the chain...

It would, but you've accidentally converted that rejection into a resolution.

The key thing about promises is that every call to then creates a new promise which is resolved/rejected based on what the then callback(s) do, and the callback processing a rejection converts that rejection into a resolution unless it intentionally does otherwise.

So here:

return step(2)
.then(null, function() { // This handler converts the
stepError(2); // rejection into a resolution
}); // with the value `undefined`

That's so that you can have error handlers that compensate for the error.

Since stepError returns a rejection, you could continue the rejection by just adding a return:

return step(2)
.then(null, function() {
return stepError(2); // Added `return`
});

...or alternately, remove that handler entirely:

return step(2);

...or you could throw in the callback, which is automatically turned into a rejection.

The unhandled rejection warning is caused by the fact nothing consumes the rejection from stepError.


Here's an example returning the result of stepError:

Promise.resolve()    .then(function() {        return step(1)            .then(null, function() {                return stepError(1); // Added `return`            });    })    .then(function() {        return step(2)            .then(null, function() {                return stepError(2); // Added `return`            });    })    .then(function() {        return step(3)            .then(null, function() {                return stepError(3); // Added `return`            });    });
function step(n) { console.log('Step '+n); return (n === 2) ? Promise.reject(n) : Promise.resolve(n);}
function stepError(n) { console.log('Error '+n); return Promise.reject(n);}

How to break promise chain nested into another promise chain

You will have to attach with promises chain, do something like this

function nestedChain()
{
stepOne()
.then(stepTwo, chainError)
.then(stepThreee, chainError)
.catch ((error) => {
console.log(error);
return undefined;
})
}

Break a Promise chain inside for-loop

It is much easier to do with the async/await syntax:

async function pro(arr) {
const self = {req: {}, res: {}};
for(const f of arr) await f.call(self);
return self;
}

async function pro(arr) {    const self = {req: {}, res: {}};    for(const f of arr) await f.call(self);    return self;}
const x = function () { const self = this; return new Promise(resolve => { self.req = {key: "value"} resolve(); }) }
const y = function () { const self = this; return new Promise((resolve, reject) => { console.log(self); reject("y failed"); })}
const z = function () { const self = this; return new Promise((resolve, reject) => { console.log('failed'); })}
pro([x, y, z]).then((self) => { console.log('final',self);}).catch(e => { console.log('error', e);});

Node.js break promise chain

You can't break promise chains in a nice way, but you can nest them:

identity.token(username, password).then(response => {
if (!token.tfa || !allow2FA) {
return res.status(200).json(token.raw);
}
return twoFactor.generateLoginOtc(username, token).then(response => {
return res.status(204).json();
})
}).catch(error => {
console.log(error);
return res.status(error.status).json(error);
});


Related Topics



Leave a reply



Submit