Question
Update:
To help future viewers of this post, I created this demo of pluma's answer.
Question:
My goal seems fairly straightforward.
step(1)
.then(function() {
return step(2);
}, function() {
stepError(1);
return $q.reject();
})
.then(function() {
}, function() {
stepError(2);
});
function step(n) {
var deferred = $q.defer();
//fail on step 1
(n === 1) ? deferred.reject() : deferred.resolve();
return deferred.promise;
}
function stepError(n) {
console.log(n);
}
The problem here is that if I fail on step 1, both stepError(1)
AND
stepError(2)
are fired. If I don't return $q.reject
then stepError(2)
won't be fired, but step(2)
will, which I understand. I've accomplished
everything except what I'm trying to do.
How do I write promises so that I can call a function on rejection, without calling all of the functions in the error chain? Or is there another way to accomplish this?
Here's a live demo so you've got something work with.
Update:
I kind of have solved it. Here, I am catching the error at the end of
the chain and passing the data to reject(data)
so that I will know what
issue to handle in the error function. This actually doesn't meet my
requirements because I don't want to depend on the data. It would be lame, but
in my case it would be cleaner to pass an error callback to the function
rather than to depend on the returned data to determine what to do.
step(1)
.then(function() {
return step(2);
})
.then(function() {
return step(3);
})
.then(false,
function(x) {
stepError(x);
}
);
function step(n) {
console.log('Step '+n);
var deferred = $q.defer();
(n === 1) ? deferred.reject(n) : deferred.resolve(n);
return deferred.promise;
}
function stepError(n) {
console.log('Error '+n);
}
Answer
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](http://promises-
aplus.github.io/promises-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 valueundefined
.