I am trying to chain promises so that the chain will break if one promise is rejected. I followed the leads of a previous SO question and tried to apply it to native promises, but I think I am misunderstanding the way things work.
Here is how I have rewritten the code:
Promise.resolve()
.then(function() {
return step(1)
.then(null, function() {
stepError(1);
});
})
.then(function() {
return step(2)
.then(null, function() {
stepError(2);
});
})
.then(function() {
return step(3)
.then(null, function() {
stepError(3);
});
});
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);
}
The output of the above code is:
Step 1
Step 2
Error 2
Step 3
[UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): 2]
In my understanding, step 2 should break the chain and step 3 should not be executed. When step(2) returns a rejected promise, stepError(2) is executed as expected. But since it returns Promise.reject(2), the function in the next then should not be executed, and since there is not catch in the end, the rejected promise of step 2 seems - as expected - to be forwarded until it exits the chain because it didn't find any handler.
What am I missing here ?
Here is a JSFiddle to play with: https://jsfiddle.net/6p4t9xyk/
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);
}
Looks like you overcomplicated it quite a bit. How about:
function step(n) {
console.log('Step '+n);
return (n === 2) ? Promise.reject(n) : Promise.resolve(n);
}
function stepError(n) {
console.log('Error '+n);
}
Promise.resolve()
.then(() => step(1))
.then(() => step(2))
.then(() => step(3)) // never here
.catch(stepError);
In general, "indented then" is an antipattern. All "thens" should be on the same level.
As @T.J.Crowder said, you forgot to return
the result of the error handler (or to throw
from it). To fix it, I'd recommend doing either
function withStepError(n, promise) {
return promise.catch(function(err) {
console.log('Error '+err+' from '+n);
throw new Error("failed at "+n);
});
}
Promise.resolve()
.then(function() {
return withStepError(1, step(1));
})
.then(function() {
return withStepError(2, step(2));
})
.then(function() {
return withStepError(3, step(3));
});
or
function getStepError(n) {
return function(err) {
console.log('Error '+err+' from '+n);
throw new Error("failed at "+n);
};
}
Promise.resolve()
.then(function() {
return step(1).catch(getStepError(1));
})
.then(function() {
return step(2).catch(getStepError(2));
})
.then(function() {
return step(3).catch(getStepError(3));
});