Flattening Promise Chains

Promises are a great solution to address complexities of asynchronous requests and responses. AngularJS provides Promises using services such as $q and $http; other services also use promises, but I will not discuss those here.

Promises allow developers to easily attach 1x-only notifications of response to any asynchronous request/action. Promises also enable two (2) other very important things. We can:

  • Transform the responses before subsequent handlers (in the chain) are notified of the response.
  • Use the response to invoke more async requests (which could generate more promises).

But even more important than the features above, Promises support easy chaining of custom activity or computations. Managing sequences or chains of asynchronous activity can be a very difficult and complex effort. Promise chains are amazing and provide means to easily build sequences of asynchronous requests or asynchronous activity.

…and we will also discuss the some of hidden anti-patterns

Below I explore and discuss the hidden power in chain promises. Or your can simply watch the egghead.io Video Tutorial:

chaining-promises video


The FlightDashboard

Consider the Travel Service shown which loads information about the user’s upcoming travel departure. Below our service shows how a a remote web service by returns a JSON data file… Remember that data calls are asynchronous and our TravelService request generates a promise to respond when the information is loaded.

  var TravelService = function( $http )
      {
        return {
          getDeparture : function( user )
          {
            return $http.get (
              URL_LAST_FLIGHT,
              { userID : user.email }
            );
          }
        };
      }

Now let’s use this service from a FlightDashboard to load the user’s scheduled flight:

var FlightDashboard = function( $scope, user, travelService )
    {
      travelService
        .getDeparture( user )
        .then( function( departure )
        {
          // Publish the departure details to the view
          $scope.departure = departure;
        });
#
      $scope.departure = null;
    };

Okay this is nice… but nothing shockingly new is shown here. So let’s add some `real-world` complexity.


Nesting Promise Chains

Now let’s assume that once we have flight details, then we will also want to check the weather forecast and the flight status.

The scenario here is a cascaded 3-call sequence: getDeparture() -> getFlight() -> getForecast()

sequential chaining

var FlightDashboard = function( $scope, user, travelService, weatherService )
    {
        // Level 1
#
        travelService
            .getDeparture( user.email )                 // Request #1
            .then( function( departure )                // Response Handler #1
            {
                $scope.departure = departure;
#
                // Level 2
#
                travelService
                    .getFlight( departure.flightID )        // Request #2
                    .then( function( flight  )              // Response Handler #2
                    {
                        $scope.flight = flight;
#
                        // Level 3
#
                        weatherService
                            .getForecast( departure.date )      // Request #3
                            .then( function( weather )          // Response Handler #3
                            {
                                $scope.weather = weather;
                            });
                    });
            });
    };

Notice how the success handler for getFlight() is passed the flight object. And the success handler for getForecast() is passed the weather object… both of which are published to the scope.

The above implementation uses deep-nesting to create a sequential, cascading chain of three (3) asynchronous requests; requests to load the user’s departure, flight information, and weather forecast.

Note that the code shown above does NOT handle errors. And any nested rejections will not be propagated properly.


Flattened Promise Chains

While this works, deep nesting can quickly become difficult to manage if each level has non-trivial logic. Promise chain nesting also requires developers to careful consider how they will manage errors within the chain segments.

I personally consider deep nesting to be an anti-pattern. Fortunately we can restructure the code for errors, clarity, and maintenance. Here we leverage the fact that a promise handler can return:

  • A value – that will be delivered to subsequent resolve handlers
  • A promise – that will create a branch queue of async activity
  • A exception – to reject subsequent promise activity
  • A rejected promise – to propagate rejections to subsequent handlers

Since promise handlers can return Promises, let’s use that technique to refactor a new implementation:

var FlightDashboard = function( $scope, user, flightService, weatherService )
    {
        travelService
            .getDeparture( user )                                           // Request #1
            .then( function( departure )
            {
                $scope.departure = departure;                               // Response Handler #1
                return travelService.getFlight( departure.flightID );       // Request #2
            })
            .then( function( flight )
            {
                $scope.flight = flight;                                     // Response Handler #2
                return weatherService.getForecast( $scope.departure.date ); // Request #3
            })
            .then( function( weather )
            {
                $scope.weather = weather;                                   // Response Handler #3
            });
#
        $scope.flight     = null;
        $scope.planStatus = null;
        $scope.forecast   = null;
    };

The important change here is to notice that the response handler returns a Promise. See how the handler for getDeparture() returns a promise for getFlight()? And the success handler for getFlight() which returns a promise for getForecast().

Remember that success handlers can either (a) return the response value, (b) throw an exception, or (c) return a **Promise**

This is a good example of a flattened promise chain approach. But I do not like this solution because I had to create my success handlers as function wrappers that essentially only call another promise-returning API. It would be great if I could eliminate those tedious function wrappers… which seem like an unnecessary, extra layers!

This is also manifest at least two other anti-patterns:
* we modified a $scope variable at each level; instead of a single-pass modification of all three (3) $scope variables.
* `getForecast()` call references `$scope.departure.date` instead of an *argument-passed reference*.


Better Refactors

What else can we do? What if we viewed each request-response as a self-contained process? Then we could chain processes…

    var FlightDashboard = function( $scope, user, travelService, weatherService )
        {
            var loadDeparture = function( user )
                {
                    return travelService
                            .getDeparture( user.email )                     // Request #1
                            .then( function( departure )
                            {
                                $scope.departure = departure;               // Response Handler #1
                                return departure.flightID;
                            });
                },
                loadFlight = function( flightID)
                {
                    return travelService
                            .getFlight( flightID )                          // Request #2
                            .then( function( flight )
                            {
                                $scope.flight = flight;                     // Response Handler #2
                                return flight;
                            });
                },
                loadForecast = function()
                {
                    return weatherService
                            .getForecast( $scope.departure.date )           // Request #3
                            .then(function( weather )
                            {
                                $scope.weather = weather;                   // Response Handler #3
                                return weather;
                            });
                };
#
            loadDeparture( user )<br />
                .then( loadFlight )
                .then( loadForecast );
#
            $scope.user       = user;
            $scope.departure  = null;
            $scope.flight     = null;
            $scope.weather    = null;
        };

Now we have three (3) intuitively-named functions: loadDeparture(), loadFlight(), and loadForecast()… all chained together in a flat chain; each segment of the chain is now a self-contained, named function.

#
    loadDeparture( user )
        .then( loadFlight )
        .then( loadForecast );
#

Each of these functions internally makes a service call, gets a promise, and attaches a success handler to the promise. And Each handler publishes something to the scope.

But two other VERY important things are now happening:

1) Returning Promises instead of data objects:

Notice that each of the chain segments (loadDeparture, loadFlight, loadWeather) returns a Promise. The important thing to realize here is the instead of returning a data object, we are returning another promise. Returning promises allows use to build chains where each segment is only resolved when the promise at the segment resolves… and that promise could itself represent a subchain.
While a segment is waiting for its promise to resolve or reject… all the remaining segments in the chain are waiting… and in fact, those segments have not even been called yet.The async requests in subsequent segments are queued and have not even been called yet.
This is promise chaining. This is very powerful.

2) Success handlers return data values:

Notice that the internal Promise success handler of each segment returns a value… a value that may be passed as an argument value when invoking the next segment of the promise chain. See how the first segment loadDeparture() returns the flightID… which is passed as an argument when invoking the call to loadFlight()? And While loadFlight() returns the flight object, the next segment loadWeather() ignores that value.

This flattened-promise chain is now really easy to understand and manage.

An anti-pattern issue still exists here. This solution has that one (1) funky hack:

Notice how the weather service had to use $scope.departure.date within its getForecast() call. loadWeatherForecast() can only directly receive a flight argument… and it does not have direct access to the flight reference.


Finally

Finally, we should consider the dependencies of each segment of the chain. Notice that not all of our requests have to be sequential [and thus wait for all previous segments to finish first]. In our scenario, the Flight and Weather service calls could be requested in parallel [independent of each other].

parallel chaining

We will use the $q.all() and the $q.spread() methods to condense our code and centralize all $scope changes.

var FlightDashboard = function( $scope, user, travelService, weatherService, $q, $log )
    {
        var loadFlight = function( user )
            {
                return travelService.getDeparture( user.email );               // Request #1
            },
            parallelLoad = function ( departure )
            {
                // Execute #2 & #3 in parallel...
#
                return $q.all([
                        travelService.getFlight( departure.flightID ),         // Request #2
                        weatherService.getForecast( departure.date  )          // Request #3
                    ])
                    .then( $q.spread( function( flight, weather )
                    {
                        $scope.departure   = departure;                        // Response Handler #1
                        $scope.flight      = flight;                           // Response Handler #2
                        $scope.weather     = weather;                          // Response Handler #3
#
             // Let's force an error to demonstrate the reportProblem() works!
#
                        throw( new Error("Just to prove catch() works! ") );
                    }));
            },
            reportProblems = function( fault )
            {
                $log.error( String(fault) );
            };
#
#
        // 3-easy steps to load all of our information...
        // and now we can include logging for of problems within ANY of the steps
#
        loadFlight( user )
            .then( parallelLoad )
            .catch( reportProblems );
#
    };

The last version is very clean and terse. I simplified even further AND I also added a exception handler!

The `$q.spread()` is a special [add-on](https://github.com/ThomasBurleson/angularjs-FlightDashboard/blob/master/lib/%24QDecorator.js) that is currently not part of AngularJS. I used `$QDecorator` to decorate the $q service and provide this feature.

Live Demo

Click here to open the Live Demo

Open Chrome Developer tools and you can breakpoint/step thru the logic and code base:

screen shot 2013-12-15 at 2 03 59 pm

Summary

Hopefully I have shown you some elegant and sophisticated techniques for chaining promises. The above chain can easily become even more complicated:

TreeOfChains

But even these complicated chains are easy to manage with the techniques that I have demonstrated.

And if this somewhat trivial example does not convince you… check out a real-world refactor of the Dash.js class DownloadRules Gist. The refactor is a Gist source with a conversation thread discussing the tradeoffs and considerations.

Readers can see how [in the [DownloadRules Gist](https://gist.github.com/ThomasBurleson/7576083)] how complex code and logic can be reduced and flattened into something very manageable and conceptually understandable.

You will have to decide whether you want to nest or flatten your promise chains. Just note that all of these approaches are simply techniques of chaining functions that either request more asynchronous activity or handle their async responses.

Flattening Promise Chains

23 Responses

  1. Useful and elegant examples, thank you!

    Stefano Scerra May 3, 2016 at 3:21 am #
  2. Excellent article. I have a question, let’s say that depending on the result of the first promise you don’t want to execute the next one, how could you do it?

    Felix Prados January 28, 2016 at 8:15 am #
  3. Grate article. Thank you.
    What software did you use to draw the digrams?

    Vo Thanh Nhan December 24, 2015 at 11:04 am #
  4. thanks for you article.
    it helped me a lot.

    Neo June 17, 2015 at 1:25 pm #
  5. Burleson,

    Thanks for a great article. I’m glad I stumbled upon as I have situations that have several chained calls and this really makes the code clearer and simpler. However, unless I missed something, you did not address how to handle failure functions. Is this something that you would be willing to share?

    Thanks,
    Rob

    Rob Callahan March 6, 2015 at 6:21 pm #
  6. Just trying to make sure I’m following correctly, but
    Should:

    Notice how the weather service had to use $scope.departure.date within its getForecast() call. loadWeatherForecast() can only directly receive a flight argument… and it does not have direct access to the flight reference.

    Read as:

    Notice how the weather service had to use $scope.departure.date within its getForecast() call. loadWeatherForecast() can only directly receive a flight argument… and it does not have direct access to the _departure_ reference.

    (switching “direct access to the flight reference” to ” direct access to the _departure_ reference”)

    Great article, thanks for sharing.

    Chad Carbert February 9, 2015 at 5:13 pm #
  7. Very helpful post, thank you. The one confusin point I found is your text about what `success`’s return value, and never using `success` in your code. I understand `then` is almost the same thing, but that’s another thing I now have to get my head around…

    benny February 4, 2015 at 7:20 am #
  8. Thank you! Refactored my code.

    Aslan January 9, 2015 at 8:49 am #
  9. Hey, great post. Looking into some patterns for deep chaining promises and this has been very useful.

    You have a typo in the ‘Better Refactors’ code, there’s a errant tag at the loadDepartures function call.

    Thanks!

    Paul McClean December 9, 2014 at 5:59 am #
  10. This is such a great article. I have referred to it many times and today managed to pull off parallel and chained promises in my code. Thanks so much for the fantastic explanation and diagrams. Really helpful!

    Jen December 5, 2014 at 10:41 pm #
  11. You need targeted visitors for your Flattening Promise Chains | The Solution Optimist website so why not try some for free? There is a VERY POWERFUL and POPULAR company out there who now lets you try their website traffic service for 7 days free of charge. I am so glad they opened their traffic system back up to the public! Sign up before it is too late: http://qrmas.info/k

    Eileen November 27, 2014 at 11:14 am #
  12. Thanks for the article, I was following it in order to learn best practices when dealing with promises, and I find it brilliant.
    However, while trying to write code similar to yours, I stumbled into a problem: the promise returned by $q.all will contain the AGGREGATED data from the previous requests.

    Thus, in my parallelLoad implementation (in which I don’t use spread) flight will contain all the data, while weather will be undefined.

    I don’t understand if I am making some kind of mistake, if this is due to the spread call, or if it was simply your error. Could you clarify the matter for me?

    Simone April 4, 2014 at 9:39 am #
    • Ok, I just understood now the meaning of the comment below:

      “I was using the $q.spread() example simply to show how one can gather` all promise results and deliver them as named arguments to a group fulfillment handler.”

      I expected this to be default behavior, it should be implemented like this in angular.
      Thank you anyway, I’ll leave here my question for reference.

      Simone April 4, 2014 at 9:41 am #
  13. Further to the last comments, how can you call getFlight and getWeather in parallel (after successful getDeparture call), but handle them independantly, such that a successful return immediately updates $scope and a failure in one does not ignore a success in the other?

    Royce Lithgo February 5, 2014 at 2:05 am #
  14. Good post but I don’t think you should use the parallel load in your final solution.

    Rather, wire everything together explicitly, eg


    departureP = getDeparture(...)
    departureP.then(getFlight)
    departureP.then(getWeather)

    This expresses the dependencies elegantly and simply without having to declare the parallel execution explicitly but through the magic of promises the flight and weather will still load in parallel.

    Taylor January 14, 2014 at 12:42 pm #
    • Did my example shown just previous [to the $q.spread() example] not do what you mentioned in your comments?
      BTW, I was using the $q.spread() example simply to show how one can gather` all promise results and deliver them as named arguments to a group fulfillment handler.

      Burleson Thomas January 21, 2014 at 10:31 am #
    • He has to use $q.all if he wants to get a promise that resolves once both of the parallel promises resolve. In your example you leave out the .then where he sets all the scope variables once both parallel promises are resolved. You’d have to modify your example to express that.

      “`
      departureP = getDeparture(…)
      flightP = departureP.then(getFlight)
      weatherP = departureP.then(getWeather)
      $q.all([
      flightP,
      weatherP
      ]).then($q.spread(function (flight, weather) {
      $scope.flight = flight;
      $scope.weather = weather;
      });
      “`

      Alex Ford February 7, 2015 at 9:06 pm #
  15. Great post 🙂

    I already knew promises, but the spread part has been very usefull for a refactor i had in mind. Thanks

    Rafinskipg January 14, 2014 at 8:37 am #
  16. This is great.
    Just to make sure I understand, in your $q.spread() you assign departure, flight, weather to the $scope. Am I correct to assume that if either travelService.getFlight *or* weatherService.getForecast fails, then *nothing* is assigned to the scope? Quoting Q: “The spread function “spreads” the values over the arguments of the fulfillment handler. The rejection handler will get called at the first sign of failure”. As you pointed out, both services are independent and certainly can/should be called in parallel, but if you can’t get the weather, wouldn’t you still want to be able to display the flight info?

    Sebastien January 3, 2014 at 11:20 am #
    • @Sebastien,
      Very good point and TRUE. My $q.spread() will not resolve unless all the specified promises resolve.
      For some scenarios this risk is allowable… but the developer should certainly be aware of such code flows.

      Burleson Thomas January 21, 2014 at 10:34 am #
Trackbacks/Pingbacks
  1. JavaScript Promise example, reverse geocode location | T. C. Mits - August 24, 2014

    […] Flattening Promise Chains […]

  2. JavaScript Promises Are Cool | Keyhole Software - July 28, 2014

    […] http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/ […]

  3. An Exploration of AngularJS - Promises, Promises - blog.credera.com - July 23, 2014

    […] The promise chaining above runs into a serious nesting problem and hurts readability, but it can be flattened because promise handlers can return promises! […]

Leave a Reply