Introduction to promise API with $q in angularjs for beginners

JavaScript programmers are accustomed to the asynchronous programming model. Both a browser and the node.js execution environments are full of asynchronous events: XHR responses, DOM events, IO and timeouts, which can be triggered at any moment and in random order. Even if, we are all used to coping with the asynchronous nature of the execution environment the truth is that asynchronous programming might be perplexing, especially when it comes to synchronizing multiple asynchronous events.

In the synchronous world chaining function calls (invoking a function with a result of another function) and handling exceptions (with try/catch) is straightforward. In the asynchronous world, we can’t simply chain function calls; we need to rely on callbacks. Callbacks are fine when dealing with just one asynchronous event, but things start to get complicated as soon as we need to coordinate multiple asynchronous events. Exceptional situation handling is particularly tough in this case.

To make asynchronous programming easier the Promise API was recently adopted by several popular JavaScript libraries. The concepts behind the Promise API are not new, and were proposed in the late seventies, but only recently those concepts made it into the mainstream JavaScript programming.

Tip

The main idea behind the Promise API is to bring to the asynchronous world the same ease of functions calls chaining and error handling as we can enjoy in the synchronous programming world.

angularjs comes with the $q service a very lightweight Promise API implementation. A number of angularjs services (most notably $http, but also $timeout and others) heavily rely on the promise-style APIs. So we need to get familiar with $q in order to use those services effectively.

Note

The $q service was inspired by the Kris Kowal’s Q Promise API library (https://github.com/kriskowal/q). You might want to check out this library to gain a better understanding or promise concepts and compare angularjs lightweight implementation with the full featured Promise API library.

Working with promises and the $q service

To learn the relatively small API exposed by the $q service, we are going to use examples from real life, just to demonstrate that the Promise API can be applied to any asynchronous events, and not only to XHR calls.

Learning $q service basics

Let’s imagine that we want to order a pizza over the phone and have it delivered to our home. The outcome of our pizza order can be either delivered food or a phone call indicating problems with our order. While ordering a pizza is just a matter of a short phone call, the actual delivery (order fulfillment) takes some time, and is asynchronous.
To get the feeling of the Promise API, let’s have a look at the pizza order, and its successful delivery, modeled using the $q service. Firstly, we are going to define a person that can consume a pizza, or just get disappointed when an order is not delivered:

var Person = function (name, $log) {

this.eat = function (food) {
    $log.info(name + " is eating delicious " + food);
  };
this.beHungry = function (reason) {
    $log.warn(name + " is hungry because: " + reason);
  }
};

The Person constructor defined above can be used to produce an object containing the eat and beHungry methods. We are going to use those methods as the success and error callbacks, respectively.

Now, let’s model a pizza ordering and fulfillment process written as a Jasmine test:

it('should illustrate basic usage of $q', function () {

  var pizzaOrderFulfillment = $q.defer();
  var pizzaDelivered = pizzaOrderFulfillment.promise;

  pizzaDelivered.then(pawel.eat, pawel.beHungry);

  pizzaOrderFulfillment.resolve('Margherita');
  $rootScope.$digest();

  expect($log.info.logs).toContain(['Pawel is eating delicious Margherita']);
});

The unit test starts by the call to the $q.defer() method which returns a deferred object. Conceptually it represents a task that will be completed (or will fail in the future). The deferred object has two roles:

  • It holds a promise object (in the promise property). Promises are placeholders for the future results (success or failure) of a deferred task.
  • It exposes methods to trigger future task completion (resolve) or failure (reject).

There are always two players in the Promise API: one that controls future task execution (can invoke methods on the deferred object) and another one that depends on the results of the future task execution (holds onto promised results).

Note

The deferred object represents a task that will complete or fail in the future. A promise object is a placeholder for the future results of this task completion.

An entity that controls the future task (in our example it would be a restaurant) exposes a promise object (pizzaOrderFulfillment.promise) to entities that are interested in the result of the task. In our example, Pawel is interested in the delivered order and can express his interest by registering callbacks on the promise object. To register a callback the then(successCallBack, errorCallBack) method is used. This method accepts callback functions that will be called with a future task result (in case of success callback) or a failure reason (in case of error callback). The error callback is optional and can be omitted. If the error callback is omitted and a future task fails, this failure will be silently ignored.

To signal the future task completion the resolve method should be called on the deferred object. The argument passed to the resolve method will be used as a value provided to the success callback. After a success callback is called a future task is completed and the promise is resolved (fulfilled). Similarly, the call to the reject method will trigger the error callback invocation and promise rejection.

Note

In the test example there is a mysterious call to the $rootScope.$digest() method. In angularjs results of promise resolution (or rejection) are propagated as part of the $digest cycle.

Promises are first-class JavaScript objects

At first glance it might look like the Promise API adds unnecessary complexity. But to appreciate the real power of promises we need to see more examples. First of all we need to realize that promises are first-class JavaScript objects. We can pass them around as arguments and return them from function calls. This allows us to easily encapsulate asynchronous operations as services. For example, let’s imagine a simplified restaurant service:

var Restaurant = function ($q, $rootScope) {

  var currentOrder;

  this.takeOrder = function (orderedItems) {
    currentOrder = {
      deferred:$q.defer(),
      items:orderedItems
    };
    return currentOrder.deferred.promise;
  };

  this.deliverOrder = function() {
    currentOrder.deferred.resolve(currentOrder.items);
    $rootScope.$digest();
  };

  this.problemWithOrder = function(reason) {
    currentOrder.deferred.reject(reason);
    $rootScope.$digest();
  };
};

Now the restaurant service encapsulates asynchronous tasks and only returns a promise from its takeOrder method. The returned promise can be then used by the restaurant customers to hold onto promised results and be notified when results are available.

As an example of this newly crafted API in action, let’s write code that will illustrate rejecting promises and error callbacks being invoked:

it('should illustrate promise rejection', function () {

  pizzaPit = new Restaurant($q, $rootScope);
  var pizzaDelivered = pizzaPit.takeOrder('Capricciosa');
  pizzaDelivered.then(pawel.eat, pawel.beHungry);

  pizzaPit.problemWithOrder('no Capricciosa, only Margherita
    left');
  expect($log.warn.logs).toContain(['Pawel is hungry because: no
    Capricciosa, only Margherita left']);
});

Aggregating callbacks

One promise object can be used to register multiple callbacks. To see this in practice let’s imagine that both authors of this book are ordering a pizza and as such both are interested in the delivered order:

it('should allow callbacks aggregation', function () {

  var pizzaDelivered = pizzaPit.takeOrder('Margherita');
  pizzaDelivered.then(pawel.eat, pawel.beHungry);
  pizzaDelivered.then(pete.eat, pete.beHungry);

  pizzaPit.deliverOrder();
  expect($log.info.logs).toContain(['Pawel is eating delicious
    Margherita']);
  expect($log.info.logs).toContain(['Peter is eating delicious
    Margherita']);
});

Here multiple success callbacks are registered and all of them are invoked upon a promise resolution. Similarly, promise rejection will invoke all the registered error callbacks.

Registering callbacks and the promise lifecycle

A promise that was resolved or rejected once can’t change its state. There is only one chance of providing promised results. In other words it is not possible to:

  • Resolve a rejected promise
  • Resolve an already resolved promise with a different result
  • Reject a resolved promise
  • Reject a rejected promise with a different rejection reason

Those rules are rather intuitive. For example, it wouldn’t make much sense if we could be called back with the information that there are problems with our order delivery after our pizza was successfully delivered (and probably eaten!).

Note

Any callbacks registered after a promise was resolved (or rejected) will be resolved (or rejected) with the same result (or failure reason) as the initial one.

Asynchronous action chaining

While aggregating callbacks is nice, the real power of the Promise API lies in its ability to mimic the synchronous function invocations in the asynchronous world.

Continuing our pizza example let’s imagine that this time we are invited to our friends for a pizza. Our hosts will order a pizza and upon order arrival they will nicely slice and serve it. There is a chain of asynchronous events here: firstly a pizza needs to be delivered, and only then prepared for serving. There are also two promises that need to be resolved before we can enjoy a meal: a restaurant is promising a delivery and our hosts are promising that a delivered pizza will be sliced and served. Let’s see the code modeling this situation:

it('should illustrate successful promise chaining', function () {

  var slice = function(pizza) {
    return "sliced "+pizza;
  };

pizzaPit.takeOrder('Margherita').then(slice).then(pawel.eat);

  pizzaPit.deliverOrder();
expect($log.info.logs).toContain(['Pawel is eating delicious sliced 
    Margherita']);});

In the previous example, we can see a chain of promises (calls to the then method). This construct closely resembles synchronous code:

pawel.eat(slice(pizzaPit));

Note

Promise chaining is possible only because the then method returns a new promise. The returned promise will be resolved with the result of the return value of the callback.

What is even more impressive is how easy it is to deal with the error conditions. Let’s have a look at the example of the failure propagation to a person holding onto a promise:

it('should illustrate promise rejection in chain', function () {

  pizzaPit.takeOrder('Capricciosa').then(slice).then(pawel.eat,
    pawel.beHungry);

  pizzaPit.problemWithOrder('no Capricciosa, only Margherita
    left');
  expect($log.warn.logs).toContain(['Pawel is hungry because: no
    Capricciosa, only Margherita left']);
});

Here the rejection result from the restaurant is propagated up to the person interested in the final result. This is exactly how the exception handling works in the synchronous world: a thrown exception will bubble up to a first catch block.

In the Promise API the error callbacks act as catch blocks, and as with standard catch blocks – we’ve got several options of handling exceptional situations. We can either:

  • recover (return value from a catch block)
  • propagate failure (re-throw an exception)

With the Promise API it is easy to simulate a recovery in catch block. As an example, let’s assume that our hosts will take an effort of ordering another pizza if a desired one is not available:

it('should illustrate recovery from promise rejection', function () {

var retry = function(reason) {
    return pizzaPit.takeOrder('Margherita').then(slice);
  };

  pizzaPit.takeOrder('Capricciosa')
.then(slice, retry)
.then(pawel.eat, pawel.beHungry);

pizzaPit.problemWithOrder('no Capricciosa, only Margherita left');
pizzaPit.deliverOrder();

expect($log.info.logs).toContain(['Pawel is eating delicious sliced 
   Margherita']);
});

We can return a new promise from an error callback. The returned promise will be part of the resolution chain, and the final consumer won’t even notice that something went wrong. This is a very powerful pattern that can be applied in any scenario that requires retries.
The other scenario that we should consider is re-throwing exceptions as it might happen that recovery won’t be possible. In such a case the only option is to trigger another error and the $q service has a dedicated method ($q.reject) for this purpose:

it('should illustrate explicit rejection in chain', function () {

  var explain = function(reason) {
    return $q.reject('ordered pizza not available');
  };

  pizzaPit.takeOrder('Capricciosa')
.then(slice, explain)
.then(pawel.eat, pawel.beHungry);

  pizzaPit.problemWithOrder('no Capricciosa, only Margherita
    left');

  expect($log.warn.logs).toContain(['Pawel is hungry because:
    ordered pizza not available']);
});

The $q.reject method is an equivalent of throwing an exception in the asynchronous world. This method is returning a new promise that is rejected with a reason specified as an argument to the $q.reject method call.

More on $q

The $q service has two additional, useful methods: $q.all and $q.when.

Aggregating promises

The $q.all method makes it possible to start multiple asynchronous tasks and be notified only when all of the tasks complete. It effectively aggregates promises from several asynchronous actions, and returns a single, combined promise that can act as a join point.

To illustrate usefulness of the $q.all method, let’s consider an example of ordering food from multiple restaurants. We would like to wait for both orders to arrive before the whole meal is served:

      it('should illustrate promise aggregation', function () {

        var ordersDelivered = $q.all([
          pizzaPit.takeOrder('Pepperoni'),
          saladBar.takeOrder('Fresh salad')
        ]);

        ordersDelivered.then(pawel.eat);

        pizzaPit.deliverOrder();
        saladBar.deliverOrder();
        expect($log.info.logs).toContain(['Pawel is eating delicious Pepperoni,Fresh salad']);
      });

The $q.all method accepts an array of promises as its argument, and returns an aggregated promise. The aggregated promise will be resolved only after all the individual promises are resolved. If, on the other hand, one of the individual actions fail the aggregated promise will be rejected as well:

it('should illustrate promise aggregation when one of the promises
  fail', function () {

  var ordersDelivered = $q.all([
    pizzaPit.takeOrder('Pepperoni'),
    saladBar.takeOrder('Fresh salad')
  ]);

  ordersDelivered.then(pawel.eat, pawel.beHungry);

  pizzaPit.deliverOrder();
  saladBar.problemWithOrder('no fresh lettuce');
  expect($log.warn.logs).toContain(['Pawel is hungry because: no fresh lettuce']);
});

The aggregated promise gets rejected with the same reason as the individual promise that was rejected.

Wrapping values as promises

Sometimes we might find ourselves in a situation where the same API needs to work with results obtained from asynchronous and synchronous actions. In this case it is often easier to treat all the results as asynchronous.

Note

The $q.when method makes it possible to wrap a JavaScript object as a promise.

Continuing our “pizza and salad” example, we could imagine that a salad is ready (synchronous action) but a pizza needs to be ordered and delivered (asynchronous action). Still we want to serve both dishes at the same time. Here is an example illustrating how to use the $q.when and the $q.all methods to achieve this in a very elegant way:

it('should illustrate promise aggregation with $q.when', function () {

  var ordersDelivered = $q.all([
    pizzaPit.takeOrder('Pepperoni'),
    $q.when('home made salad')
  ]);

  ordersDelivered.then(pawel.eat, pawel.beHungry);

  pizzaPit.deliverOrder();
  expect($log.info.logs).toContain(['Pawel is eating delicious Pepperoni,home made salad']);
});

The $q.when method returns a promise that is resolved with a value supplied as an argument to the when method call.

$q integration in angularjs

The $q service is not only a quite capable (yet lightweight!) Promise API implementation, but also it is tightly integrated with the angularjs rendering machinery.

Firstly, promises can be directly exposed on a scope and rendered automatically as soon as a promise is resolved. This enables us to treat promises as model values. For example, given the following template:

<h1>Hello, {{name}}!</h1>

And the code in a controller:

$scope.name = $timeout(function () {
      return "World";
}, 2000);

The famous “Hello, World!” text will be rendered after two seconds without any manual programmer’s intervention.

Note

The $timeout service returns a promise that will be resolved with a value returned from a timeout callback.

As convenient as it might be this pattern results in code that is not very readable. Things can get even more confusing when we realize that promises returned from a function call are not rendered automatically! The template markup is as follows:

<h1>Hello, {{getName()}}!</h1>

And the following code in a controller:

$scope.getName = function () {
      return $timeout(function () {
        return "World";
      }, 2000);
};

This code won’t yield the expected text in a template.

Tip

We advise against exposing promises directly on a $scope and relying on the automatic rendering of resolved values. We find this approach is rather confusing, especially taking into account inconsistent behavior for promises returned from a function

Deven Rathore

Deven is an Entrepreneur, and Full-stack developer, Constantly learning and experiencing new things. He currently runs CodeSource.io and Dunebook.com.

Published by
Deven Rathore

Recent Posts

Are There any Similarities Between Web Design and Art?

You may be surprised at how many of the same skills are involved!  Let’s get…

1 day ago

Tips on Increasing your organic traffic on WordPress

Introduction  As more and more people are using online services nowadays, most of the business…

2 days ago

Five Reasons You Should Start a Social Media Campaign

Small businesses need all the advertisements they can get. Traditional avenues for advertising, such as…

3 days ago

Top 10 SEO Writing Tips for Beginners 2021

Search Engine Optimization. It’s complicated, to say the least. Search engines, like Google, are constantly…

2 weeks ago

Should you become a freelancer in 2021? Pros and Cons

Freelancing and working from home was long considered idyllic by many, but the global pandemic…

2 weeks ago

The Leading Renewable Energy Trends in 2021

The past year saw slowdowns in the ongoing shift toward renewable energy in many countries…

2 weeks ago