AngularJS’s promises library ($q) provides two different patterns for handling success and failure: the callback pattern and the promise chain pattern (promise.then().catch()). In this article, we’ll look in-depth at the two different patterns, and see why you should always use the promise chain pattern, and why the callback pattern is a much abused Anti-Pattern.

Tl;dr – How to handle a promise’s return value

You can handle a promise’s return value using either the callback pattern or the promise chain pattern. Always use the promise chain pattern as its catch() function handles both a rejected promise and any errors in your success function.

var promise = $q.deferred();

// 1: The promise chain pattern. Use this always:
promise.then(successHandlerFn)
       .catch(failureHandlerFn); // catches errors in the success handler
                                 // as well as a rejected promise

// 2: The callback anti-pattern. NEVER use this
promise.then(successCallbackFn, failCallbackFn);

NEVER mix the two patterns:

// NEVER do this!
promise.then(successHandlerFn, failureHandlerFn)
       .catch();

Why you should always use the Promise Chain Pattern

Both patterns provide a mechanism for handling a successful promise and a rejected one. With the callback pattern, you supply two callback functions, the first of which is called on success, while the second is called on error:

// The callback pattern ( Note: we’ll ignore the third optional
// notifyCallback parameter for now):

promise.then(successCallback, failCallback);

The second parameter (failCallback) is used to handle a promise being rejected when any errors occur. In this situation, it’s identical to using .catch(). However, if an error occurs in your successCallback() function, your code will blow up.

In contrast, with the promise chain pattern, any error in promise.then(successHandlerFn) will be passed to the promise.catch(failureHandlerFn) function, where it can be safely handled without blowing up.

In this way, .catch() really is equivalent to the catch clause of a try...catch block for both your success handler and a rejected promise.

Why do we have both patterns?

So why does the callback pattern exist if it’s so heinous? It’s actually the way the $q library implements the then() function within Angular; the catch() pattern is largely syntactic sugar, and is simply a wrapper around the callback pattern with the successHandler set to null, like so:

// AngularJS's implementation of catch
Promise.prototype = {
 "catch": function(callback) {
   return this.then(null, callback);
 }

However, a welcome side-effect of this syntactic sugaring is that it catches any error in the success handler, which is why you should always use it.

The callback pattern is actually an Anti-pattern

The callback pattern has another downside, though – it’s actually an anti-pattern. Promises were designed as a (partial) solution to callback hell, that coding nightmare you face where your callbacks call more callbacks in an ever increasing pyramid of asynchronous coding doom.

So using callbacks to handle a promise’s return value makes absolutely no sense, as you’re simply replacing the promise with the very thing it was designed to replace in the first place!

The Tao of Code calls the callback pattern the ‘Overly keen error handler’, which is quite appropriate. The error handler steps up to the plate when the promise is rejected, but thinks its job is done when the success callback is executed, and so will refuse to run should an error happen within it. Overly keen, see. Nobody likes keenness.

Tyler Russell goes one step further and calls it ‘The Anti-Pattern that makes me cry’ and he’s right – it’s the easiest way to make grown AngularJS coders blub like a baby. So always use promise chains.

Using promise chains in IE8

Should you have the misfortune of having to support IE8, you’ll need to format the catch() syntax differently, as IE8 supports ECMAScript 3, not 5. As such, you’ll need to format it as follows:

promise.then(successHandlerFn)
[‘catch’](function failureHandlerFn(){...})

This will work in all browsers from IE8 upwards, including those that support ES3, 5 and 6.

IE8, I hate you.

Heinous Mixing of Patterns

So what happens when you mix the callback pattern and the promise chain pattern, as in the following code snippet?:

// Never do this!
promise.then(successCallback, failureCallback)
       .catch();

If the promise is rejected, the failureCallback is called and the catch() is ignored. If the promise resolved successfully, but there’s an error in the successCallback, the successCallback is called, the error occurs, and then the catch() function is called. In this case, the failureCallback is completely ignored.

Although you may be tempted to use this hybrid approach, avoid the temptation. It’ll lead to confusion amongst your team and your future self, and no-one will be sure what function is called when.

If you need to manage errors in your success handler differently from a rejected promise, use try...catch() in your success handler, and leave catch() purely for handling a rejected promise.

Best Practice

  • Always use the promise chain pattern. You’ll catch errors from both a rejected promise and an error in your success handler. In both cases, what you aimed to do with your promise has failed, just in different ways. You still need to clean up and report the error, so in most cases, the same code will suffice for both types of failure.
  • If you need to manage a rejected promise differently from an error that occurs in your success handler, use try...catch() in your success handler.
  • Never mix the callback pattern with the promise chain pattern.
  • Never use the callback pattern with promises. Period!
There are no comments.

Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>