Home » Javascript » When is .then(success, fail) considered an antipattern for promises?

When is .then(success, fail) considered an antipattern for promises?

Posted by: admin November 30, 2017 Leave a comment

Questions:

I had a look at the bluebird promise FAQ, in which it mentions that .then(success, fail) is an antipattern. I don’t quite understand its explanation as for the try and catch.
What’s wrong with this the following?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

It seems that the example is suggesting the following as the correctly way.

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

What’s the difference?

Answers:

What’s the difference?

The .then() call will return a promise that will be rejected in case the callback throws an error. This means, when your success logger fails, the error would be passed to the following .catch() callback, but not to the fail callback that goes alongside with success.

Here’s a control flow diagram:

control flow diagram of then with two arguments
control flow diagram of then catch chain

To express it in synchronous code:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

The second log (which is like the first argument to .then()) will only be executed in case that no exception happened. The labelled block and the break statement feel a bit odd, this is actually what python has try-except-else for (recommended reading!).

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

The catch logger will also handle exceptions from the success logger call.

So much for the difference.

I don’t quite understand its explanation as for the try and catch

The argument is that usually you want to catch errors in every step of the processing, and that you shouldn’t use it in chains. The expectation is that you only have one final handler which handles all errors – while, when you use the “antipattern”, errors in some of the then-callbacks are not handled.

However, this pattern is actually very useful: When you want to handle errors that happened in exactly this step, and you want to do something entirely different when no error happened – i.e. when the error is unrecoverable. Be aware that this is branching your control flow. Of course, this is sometimes desired.


What’s wrong with this the following?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

That you had to repeat your callback. You rather want

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

You also might consider using .finally() for this.

Questions:
Answers:

The two aren’t quite identical. The difference is that the first example won’t catch an exception that’s thrown in your success handler. So if your method should only ever return resolved promises, as is often the case, you need a trailing catch handler (or yet another then with an empty success parameter). Sure, it may be that your then handler doesn’t do anything that might potentially fail, in which case using one 2-parameter then could be fine.

But I believe the point of the text you linked to is that then is mostly useful versus callbacks in its ability to chain a bunch of asynchronous steps, and when you actually do this, the 2-parameter form of then subtly doesn’t behave quite as expected, for the above reason. It’s particularly counterintuitive when used mid-chain.

As someone who’s done a lot of complex async stuff and bumped into corners like this more than I care to admit, I really recommend avoiding this anti-pattern and going with the separate handler approach.

Questions:
Answers:

By looking at advantages and disadvantages of both we can make a calculated guess as to which is appropriate for the situation.
These are the two main approaches to implementing promises. Both have it’s pluses and minus

Catch Approach

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Advantages

  1. All errors are handled by one catch block.
  2. Even catches any exception in the then block.
  3. Chaining of multiple success callbacks

Disadvantages

  1. In case of chaining it becomes difficult to show different error messages.

Success/Error Approach

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

Advantages

  1. You get fine grained error control.
  2. You can have common error handling function for various categories of errors like db error, 500 error etc.

Disavantages

  1. You will still need another catch if you wish to handler errors thrown by the success callback