Back

TechnologyJul 23, 2014

An Exploration of AngularJS – Promises, Promises

Aaron Wolin

WHAT IS ANGULARJS?

AngularJS is a powerful MV* web framework for developing web applications. Its deep feature set  includes two-way binding, custom HTML elements called directives, inline filtering and sorting, a “jQuery lite” implementation, and an extensible modular system. Throw in a great community of developers and open-source modules, and it’s easy to see why AngularJS has become a popular choice for web apps.

There are many great tutorials on AngularJS that provide a starting point for app development. This post is one of a series providing insight on some more advanced concepts of developing an AngularJS application.

PROMISE/DEFERRAL

A core concept in AngularJS development is the pattern of promising what to do when an action is completed and deferring the execution of the promise.

It’s often helpful to think of promise/deferral in terms of what, which, and when. The promise object has control over what will happen when an action is resolved, but the deferrer has control over which action is resolved and when the promise is resolved. Take the following traffic example:

  • The car will drive through an intersection if the stoplight is green.

The car controls what will happen—either going or stopping. But the light controls which action the car will take (go forward or stop) and when the car will take it (only when the light is green).

The promise/deferral pattern is useful for handling asynchronous events, especially when interacting with external services where execution is out of the developer’s control.

$HTTP SERVICE

A common usage of a promise/deferral is an asynchronous HTTP request to a web server. The code initializes a promise that the web server will respond to the request (either with a successful resolution or a failure), and the data from the server will be handled once it is returned. In these cases the deferrer is an external source, i.e., the server.

AngularJS’s $http module is a specific implementation of the promise/deferral pattern. The $http request returns a promise with the implementation-specific success and error methods.

// Create a promise controlling what will happen when the URL returns data or errors     $http({ method: 'GET', url: '/requestUrl' }).success(function(data, status, headers, config) {              // Called asynchronously if the server responds with data         }).error(function(data, status, headers, config) {             // Called asynchronously if the request errors out         });

$Q SERVICE

AngularJS contains more generic promise/deferral APIs in the $q module.

The typical pattern is to:

  1. Create a deferred object using var defer = $q.defer();

  2. Either resolve() or reject() the deferred object

  3. Use the deferred.promise to handle the resolve or reject options

The example below shows our example car/intersection in AngularJS code. We created a simple car that starts at an intersection and waits until the light is green. The car will then proceed to drive forward if the light is green.

First, we create a Light object with a function that will return a promise. The promise will only resolve when the light is green, which is every other minute on the hour.

var Light = function() {};  /**  * Returns a promise that resolves when the light is green.  * The promise will be rejected if an error occurs.  */ Light.prototype.promiseLightWillBeGreen = function() {     var defer = $q.defer();          // Checks a stop light is green every second for 60 seconds.      try {         $interval(function() {             // The light is green every other minute             if (new Date().getMinutes() % 2 === 0) {                  defer.resolve(true);             } }, 1000, 60);     }     // Any unintended exceptions would cause the light never to be green until the user checks again     catch (e) {         deferred.reject(false);         }          return defer.promise; }

A Car object is then assigned a handleIntersection(light) method that controls when the car will proceed to the next intersection.

var Car = function() {};  // Drive the car forward.  Not implemented here for simplicity.  Car.prototype.drive = function() { ... };  // Drive through an intersection only when the light is green  Car.prototype.handleIntersection = function(light) {     light.promiseLightWillBeGreen()                             // Handle the promise         .then( function (results) { // Light is green (success!)                  car.drive(); }, function (error) {                                  // Light is broken (an error occurred or it was rejected)                 alert("Error getting Foo count. " + error);             }); }

Finally, we create a single light and car and assume that the car starts at an intersection. The car will call its drive() method only when the light.promiseLightWillBeGreen() resolves:

var light = new Light(); var car = new Car();  // Assume that the car starts at a stoplight; handles how the car proceeds car.handleIntersection(light);

CHAINING PROMISES

Multiple promises often need to be completed sequentially in an application. In the example above, suppose a car has many lights to drive through. What do you do then?

Let’s define another function for the car that promises it will drive to the next intersection:

// Stop the car.  Not implemented here for simplicity.  Car.prototype.stop = function () { ... };    // Check if a car is at an intersection.  Not implemented here for simplicity.  Car.prototype.isAtIntersection = function () { ... };  // Drive the car to the next intersection.  Car.prototype.driveToNextIntersection = function() { var defer = $q.defer(); // Start to drive this car forward var thisCar = this; thisCar.drive(); // Check every second if a car is at an intersection, and stop the car if so     try { var intersectionPolling = $interval(function() { if (thisCar.isAtIntersection()) { // Resolve that we are at an intersection defer.resolve(true); // Stop checking if the car is at an intersection $interval.cancel(intersectionPolling); }}, 1000);     }     // Any unintended errors would cause the car to drive forward until stopped outside of this method     catch (e) { deferred.reject(false); } return defer.promise; }

We can then have a series of lights that the car must move through:

var light1 = new Light(); var light2 = new Light(); var light3 = new Light(); ... var lightN = new Light();  var car = new Car();

And we can chain our car’s promises to move it through every light:

// Drive to the first intesection car.driveToNextIntersection().then(function () {     // Handle the first intersection     car.handleIntersection(light1).then(function() {         // Light 1 is green, proceed to the next intesection and handle it car.driveToNextIntersection().then(function() { car.handleIntersection(light2).then(function() { car.driveToNextIntersection().then(function() { ... car.handleIntersection(lightN).then(function() { car.drive(); alert('Drove through all intersections!'); });              ... }) }) }) })})

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

car.driveToNextIntersection().then(function () {         return car.handleIntersection(light1);}).then(function () { return car.driveToNextIntersection();     }).then(function () { return car.handleIntersection(light2);     }).then(function () { return car.driveToNextIntersection();}).then(function () { ...  }).then(function () { return car.handleIntersection(lightN);}).then(function () { car.drive(); alert('Drove through all intersections!');});

That’s much more readable, and the handleIntersection/driveToNextIntsersection chaining could be extracted out to a more general function to handle any number of intersections.

CONCLUSION

We’ve explored AngularJS promises here with an example of a car moving through a stoplight. The promise/deferral pattern is a powerful tool for handling asynchronous data, HTTP requests, and custom methods and functionality. Keep checking back for more AngularJS tricks and tips!