0

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.

Julien Zakaib
  • 196
  • 10
  • This is a known bug. See: http://stackoverflow.com/questions/31962655/recovering-from-rejected-promises-in-js – baohouse Mar 16 '17 at 00:48

3 Answers3

4

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.

Community
  • 1
  • 1
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
1

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');
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

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.

rasmeister
  • 1,986
  • 1
  • 13
  • 19
  • Right, I understand what you are getting at, but the docs very clearly say about the success / fail filters that _"they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks."_ . So what I want to know is why the code is not acting like the docs say it should. – Julien Zakaib Mar 16 '17 at 00:39
  • Please provide a link to where you are getting that information. – rasmeister Mar 16 '17 at 00:42