AngularJS Corner – Using promises and $q to handle asynchronous calls

by
Tags: , ,
Category:

Updated 10/26/2015 – for information on how Angular2 handles asyncronous calls, take a look at my latest post, Angular2 Observables and Http – Separating Services and Components, or see all of Chariot’s blog posts on Angular2.

The AngularJS $q service is said to be inspired by Chris Kowal’s Q library (github.com/kriskowal/q). The library’s goal is to allow users to monitor asynchronous progress by providing a “promise” as a return from a call. In AngularJS, the semantics of using a promise are:

var promise = callThatRunsInBackground();
promise.then(
  function(answer) {
    // do something
  },
  function(error) {
    // report something
  },
  function(progress) {
   // report progress
  });

A number of Angular services return promises: $http, $interval, $timeout, for example. All promise returns are single objects; you’re expected to research the service itself to find out what it returns. For example, for $http.get, the promise returns an object with four keys: data, status, headers, and config. Those are the same as the four parameters fed to the success callback if you use those semantics:

// this
$http.get('/api/v1/movies/avengers')
  .success(function(data, status, headers, config) {
     $scope.movieContent = data;
  });
// is the same as
var promise = $http.get('/api/v1/movies/avengers');
promise.then(
  function(payload) {
    $scope.movieContent = payload.data;
  });

We’re ignoring the error function and the promise’s error and update callbacks for this example. Beginners might wonder why we’re going to such lengths just to get an answer. But anybody who starts writing non-trivial services hits a wall in Angular – how do you pass a return back to a caller when the service returns before a callback is triggered?

Promises and Services

The semantics of Angular dictate that you use promises as a sort of ‘callback handle’ – do something asynchronous in a service, return a promise, and when the asynchronous work is done, the promise’s then function is triggered. With that in mind, let’s build a simple controller and service sample for getting data, and putting it on the page:

angular.module('atTheMoviesApp', [])
  .controller('GetMoviesCtrl',
    function($log, $scope, movieService) {
     $scope.getMovieListing = function(movie) {
       var promise =
           movieService.getMovie('avengers');
       promise.then(
          function(payload) {
              $scope.listingData = payload.data;
          },
          function(errorPayload) {
              $log.error('failure loading movie', errorPayload);
          });
     };
  })
  .factory('movieService', function($http) {
    return {
      getMovie: function(id) {
         return $http.get('/api/v1/movies/' + id);
      }
    }
  });

Now you’re able to call the $http method asynchronously, get a result and update your user interface. The service doesn’t have to understand the UI semantics – you’re not passing $scope into your getMovie function, so all is good. Except…

What about post processing

The downside of blindly passing your $http.get promise back to the controller is that the controller has to deal with the result itself. What if you want the service to post-process the result instead? More importantly, what if you want to deal with the $http errors in the service layer, so that the controller doesn’t need to handle redirects, 404s, etc?

In more sophisticated cases you can solve this by building your own promise. Let’s refactor the service to use a promise internally, so we can handle the result in the service and bring back the payload we want.

  ...
  .factory('movieService', function($http, $log, $q) {
    return {
     getMovie: function(movie) {
       var deferred = $q.defer();
       $http.get('/api/v1/movies/' + movie)
         .success(function(data) {
            deferred.resolve({
               title: data.title,
               cost: data.price});
         }).error(function(msg, code) {
            deferred.reject(msg);
            $log.error(msg, code);
         });
       return deferred.promise;
     }
    }
   });

Hopefully, the reason for building a nested promise structure is obvious – you can control both the input and output of the call, log errors appropriately, transform the output, and even provide status updates with deferred.notify(msg).

But there is a better, more foolproof way…

Work At Chariot

If you value continual learning, a culture of flexibility and trust, and being surrounded by colleagues who are curious and love to share what they’re learning (with articles like this one, for example!) we encourage you to join our team. Many positions are remote — browse open positions, benefits, and learn more about our interview process below.

Careers

Composing Promises

You can avoid creating your own deferred object and managing a separate promise by transforming your response within a then method and returning a transformed result to the caller automatically. For example, let’s say you want to wrap the data of an $http response without having to deal with your own deferred object. Here is a service method that will do so:

this.getMovie = function(movie) {
    return $http.get('/api/v1/movies/' + movie)
           .then(
              function (response) {
                return {
                   title: response.data.title,
                   cost:  response.data.price
                });
              });
};

Now the content returned in the then of the service method will be a chained promise that transforms the output. The controller then can do the same non-http work that it did in the above example:

   $scope.getMovie = function(movie) {
      service.getMovie(movie)
      .then(function(movieData) {
         $scope.movieData = movieData;
      });
   };

So, we’ve reduced the amount of code we have to write to achieve the same result, and we don’t have to worry about what happens when the Ajax call fails – it will just fail before calling the chained then function.

Handling problems in nested service calls

Now that we’re using an inner return in the success function, the promise code will automatically sense the lack of an error function and just abort the call with the default $http error object. Nice, eh? But you may wonder how you can transform the error? Simple, just throw it!

this.getMovie = function(movie) {
    return $http.get('/api/v1/movies/' + movie)
           .then(
              function (response) {
                return {
                   title: response.data.title,
                   cost:  response.data.price
                });
              },
              function (httpError) {
                 // translate the error
                 throw httpError.status + " : " +
                       httpError.data;
              });
};

…and now the error returned will be a single string, not the $http error with data, status, headers and config properties.

Doing more than one thing at a time

This is a powerful service. Even more interesting is a case when you need to process a number of asynchronous activities simultaneously. The $q.all function lets you trigger several callbacks at the same time, and use a single then function to join them all together.

I put together a sample using $q.all to show you how the async semantics work when dealing with a few in-flight promises. Here are the highlights.

In this sample I’m allowing the user to fetch several URLs at the same time using $http.get. The user enters each URL in a text input box, and when ready clicks a button to trigger the calls. A single service processes them, awaits all answers, and returns a message.

service('asyncService', function($http, $q) {
      return {
        loadDataFromUrls: function(urls) {
          var deferred = $q.defer();
          var urlCalls = [];
          angular.forEach(urls, function(url) {
            urlCalls.push($http.get(url.url));
          });
          // they may, in fact, all be done, but this
          // executes the callbacks in then, once they are
          // completely finished.
          $q.all(urlCalls)
          .then(
            function(results) {
            deferred.resolve(
             JSON.stringify(results))
          },
          function(errors) {
            deferred.reject(errors);
          },
          function(updates) {
            deferred.update(updates);
          });
          return deferred.promise;
        }
      };
    });

In this example, the payload is an array of objects containing an ‘url’ property:

  [
   { url: 'ajax1.html' },
   { url: 'ajax2.html' },
   { url: 'ajax3.html' }
  ]

For each URL, a promise is created by executing $http.get, and the promise is added to an array. The $q.all function accepts an array of promises, and converts all results into a single promise which contains an object with each answer. You can envision the result like this:

  [
    promise1_result_payload,
    promise2_result_payload,
    promise3_result_payload
  ]

If those three calls were made, the code above converts the results to JSON and returns them to the caller.

What about failures?

The difficulty in dealing with multiple in-flight promises this way is in dealing with a failure case. If any of the promises we’re waiting on fails, the entire batch fails. Try playing with the plunker link above and entering an off-site url like http://google.com. You’ll see that the entire call becomes a failure.

You still need infrastructure and architecture

Getting on my soapbox here, for anything business-related multiple in-flight promises are a bad idea. Providing a server infrastructure with transactional semantics, orchestration, and a coarse-grained single call for the client seems to be the rational way to go. I can see multiple in-flight promises for aggregating content such as images for a mosaic, but for anything data related it’s a no-brainer to avoid. And imagine the concept of a transaction in this world, then forget about it because you’re running in a browser.

Wrap-up

In short, promises fuel AngularJS asynchronous operations. Anything needing to run in the background will need to coordinate with a caller such as a controller or directive, and the promise API is the way to go.

Note – I added an example that uses a return keyword and eliminates the deferred and promise inside the service, which greatly reduces the complexity of the service.