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
JavaScript Use Variable as Object Name
How to Automatically Reload a Page After a Given Period of Inactivity
Referencing Another Schema in Mongoose
New Line in JavaScript Alert Box
Convert Js Date Time to MySQL Datetime
Differencebetween Parseint(String) and Number(String) in JavaScript
JavaScript Can't Access Private Properties
Displaying a Number in Indian Format Using JavaScript
Canvas Image Crossplatform Insecure Error
How to Shuffle the Characters in a String in JavaScript
Is a JavaScript Array Index a String or an Integer
How to Serialize an Object into a List of Url Query Parameters
Leaflet Drawing Tiles Disjointly
Why Does JavaScript Hoist Variables
How to Break Nested Loops in JavaScript
Javascript: Simple Way to Check If Variable Is Equal to One of Two or More Values
How to Remove Only the Parent Element and Not Its Child Elements in JavaScript