3

I'm having trouble with chaining .then() calls for a promise. When executing the following code:

var prom = new Promise(function(resolve, reject) {
  //let's always fail
  reject(Error("buuuu!"));
});

var thenable = 
prom.then(
    function(done) {
        console.log("First handler: Done!: Argument: ", done);
        return "First then: DONE";
    },
    function(fail) {
        console.error("First handler: Fail!. Argument: ", fail);
        return "First then: FAIL";
    }
).then(
    function(done) {
        console.info("Second handler: Done!.  Argument: ", done);
    },
    function(fail) {
        console.error("Second handler: Fail!.  Argument: ", fail);
    }
);

This prints the following in the console:

First handler: Fail!. Argument:  Error {stack: (...), message: "buuuu!"}
Second handler: Done!.  Argument:  First then: FAIL


Why does the second then() has its done handler called instead of the fail one?

Is this a bug with Chrome? (Note that I'm only interested in Google Chrome's behavior)

Do I need to resort to returning pre-resolved/rejected Promises from the .then handlers?

MrFusion
  • 912
  • 7
  • 16

2 Answers2

6

Why does the second then() has its done handler called instead of the fail one?

Because you handled the error in the first then() of the chain already, making the promise resolve with the "First then: FAIL" string that you returned from it.

Is this a bug with Chrome?

No, that's how promises are supposed to work.

Do I need to resort to returning pre-resolved/rejected Promises from the .then handlers?

You can do that, yes. Other ways to trigger the second fail handler would be:

  • Simply omit the first error handler:

    prom.then(function(done) {
        console.log("First handler: Done!: Argument: ", done);
        return "First then: DONE";
    }).then(function(done) {
        console.info("Second handler: Done!.  Argument: ", done);
    }, function(fail) {
        console.error("Second handler: Fail!.  Argument: ", fail);
    });
    
  • Or rethrow an exception (that's similar to returning a rejected promise):

    prom.then(function(done) {
        console.log("First handler: Done!: Argument: ", done);
        return "First then: DONE";
    }, function(fail) {
        console.error("First handler: Fail!. Argument: ", fail);
        throw new Error("First then: FAIL"); // or: throw fail;
        // alternatively, you can return a rejected promise:
        return Promise.reject(new Error("First then: FAIL"));
    }).then(function(done) {
        console.info("Second handler: Done!.  Argument: ", done);
    }, function(fail) {
        console.error("Second handler: Fail!.  Argument: ", fail);
    });
    
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Or in the fail case you could just return the rejected promise. – lemieuxster May 29 '14 at 21:48
  • You mean `return prom`? Yeah, that would probably work as well, but be a little odd. You just might have attached both handlers to it right away then: `prom.then(donehandler1, failhandler1); prom.then(donehandler2, failhandler2);` – Bergi May 29 '14 at 21:51
  • Thanks! I kind of assumed that the "failed" condition would be persistent across the chain. – MrFusion May 29 '14 at 22:07
  • Also, if I'm not mistaken, calling `.then` two times directly on `prom` would make both execute synchronously, as opposed to chaining `.then().then()` – MrFusion May 29 '14 at 22:08
  • Well, it bubbles ("persists") on the chain until it is handled. Yes, calling `then` twice on the same promise does "fork" the execution flow. Not sure what you need, you'd need to post real-world code instead of the example. – Bergi May 30 '14 at 00:06
  • Using `.catch` would be better for many reasons: passing multiple functions is unreadable for the same reason as passing multiple booleans `doSomething(true, false, false, true)` (although you can fix this by naming the functions), it makes the connection to normal try catch statement more obvious and finally it also handles any errors in the simultaneously passed fulfilled handler that would otherwise be silently swallowed. – Esailija May 30 '14 at 06:51
  • @Esailija: Well, it depends on whether you *want* to handler errors from the `fulfilled` handler or not. Oftentimes you do not, and then you cannot use `.then().catch()`. – Bergi May 30 '14 at 06:58
  • That actually makes no sense, why would you want to silence errors like that :| and often too? – Esailija May 30 '14 at 08:08
  • @Esailija: I didn't say I wanted to silence them. I only want to handle them later in the chain. – Bergi May 30 '14 at 13:27
6

Why does the second then() has its done handler called instead of the fail one?

Let's look at how your code looks in the synchronous world:

var value;
try{
    try {
        throw new Error("buuu!");
        console.log("First handler: Done!: Argument: ");
        value = "First then: DONE";
    } catch(e){
        console.error("First handler: Fail!. Argument: ", e);
        value = "First then: FAIL";
    }
    console.info("Second handler: Done!.  Argument: ", value);
}catch(e){
    console.error("Second handler: Fail!.  Argument: ", e);
}

When you handle an error in a catch clause, just like when you handle an error in a then clause, you recover from it. The promises example is exactly like our synchronous one.

If you want to signal that you performed your recovery logic but are still in a rejected state on the chain, rethrow. That's how it's always done in synchronous code.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Thanks! Your answer is just as good, but the other one was first. I gave you an upvote, anyway – MrFusion May 29 '14 at 22:09
  • I gave the other answer an upvote too :) I just wanted to give perspective compared to synchronous inspection. – Benjamin Gruenbaum May 29 '14 at 22:16
  • +1. I thought about including that analogy in my answer, but somehow knew that the inevitable answer of @BenjaminGruenbaum would take this path of explanation :-) – Bergi May 30 '14 at 00:08