Reputation: 194
I have a promise that can resolve or reject. I want to do something specific in those cases, and then continue resolving the promise chain (essentially I want to "catch" the rejected promise, do something, then continue resolving).
Here's a functional snippet that shows the issue I'm running into :
var def = $.Deferred();
def.then(
function() {
console.log('first success handler');
},
function() {
console.log('first fail handler');
return $.Deferred().resolve();
}
);
def.then(
function() {
console.log('second success handler');
},
function() {
console.log('second fail handler');
}
);
def.done(function() {
console.log('done handler');
});
def.reject();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Expected result :
first fail handler
second success handler
done handler
Current result :
first fail handler
second fail handler
According to jQuery docs :
As of jQuery 1.8, the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function[...]. These filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks.
So I have no idea why this is not working. I expected the returned resolved promise in the first fail handler to allow the rest of the promise chain to continue resolving.
Upvotes: 0
Views: 2407
Reputation: 664185
The important part of the docs is
the
deferred.then()
method returns a new promise
You were however throwing away that return value, and did invoke the next .then(…)
on the original def
.
You will want to use
var p = $.Deferred().reject().promise();
p.then(function() {
console.log('first success handler');
}, function() {
console.log('first fail handler');
return $.Deferred().resolve();
}).then(function() {
//^^^^^ chain directly on the result
console.log('second success handler');
}, function() {
console.log('second fail handler');
}).then(function() {
//^^^^^
console.log('done handler');
});
Upvotes: 1
Reputation: 19298
The pattern ...
var def = $.Deferred();
def.then(successHandler, errorHandler);
def.then(successHandler, errorHandler);
// etc.
... forms two (or more) branches, each then()
being dependent only on def
. Each branch possesses independent filtering power, but it is not exploited.
That is very different from ...
var def = $.Deferred();
def.then(successHandler, errorHandler).then(successHandler, errorHandler); // etc.
... which forms a single chain (without branching). The first then()
is dependent on def
, and the second then()
dependent on the first then()
. Here, the filtering power of the first then()
is exploited by chaining another then()
(and so on).
Therefore, you will get the expected output by converting the code in the question to the second pattern :
var def = $.Deferred();
def.then(function() {
console.log('first success handler');
}, function() {
console.log('first fail handler'); // (1)
return $.Deferred().resolve();
}).then(function() {
console.log('second success handler'); // (2)
}, function() {
console.log('second fail handler');
}).done(function() {
console.log('done handler'); // (3)
});
def.reject();
In a nutshell, that's what promise chaining is all about.
But don't forget about branching completely. In certain circumstances, it is essential. In this answer for example, batchRequests()
returns _p
, which can be further chained by the caller (that's one branch), but also forms its own private branch with _p.then(..., ...)
. Don't worry if you can't follow it completely - it's fairly complicated - for now trust me that branching is an essential part of the solution.
Upvotes: 4
Reputation: 1996
What your code is effectively doing is queuing two handlers (through the then
functions). When the deferred is rejected it can no longer be altered.
When the Deferred is rejected, the first then
handler fires writing your first console message.
The line return $.Deferred().resolve();
will create a new resolved Deferred but it is not "return"ed to your second then
since that handler is already tied to the first Deferred instance.
Consequently, your second then
handler will now fire also hitting the failFilter (the handler for when the Deferred is rejected).
One approach you could take is to pass a value when you reject (or resolve) your Deferred. You can then receive that response and take remedial action as shown in this (somewhat contrived) sample:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<div id="test"></div>
<script>
var def = $.Deferred();
def.then(
function () {
console.log('first success handler');
},
function (response) {
console.log('first fail handler');
console.log(response);
}
);
def.then(
function () {
console.log('second success handler');
},
function (response) {
if (response === 999) {
// do something to recover from earlier reject
console.log('recovery action initiated');
} else {
console.log('second fail handler');
}
}
);
def.done(function () {
console.log('done handler');
});
def.reject(999);
</script>
</body>
</html>
I know of no way to interject between the two then
handlers, they are simply two queued handlers to the same Deferred.
Upvotes: 0