Click here to Skip to main content
15,867,453 members
Articles / AngularJs

AngularJS Promises - The Definitive Guide

Rate me:
Please Sign up or sign in to vote.
5.00/5 (16 votes)
7 May 2014CPOL11 min read 51.4K   24   10
Angular promises - the definitive guide

Promises are a core feature of AngularJS - whether you understand them or not, if you use AngularJS you've almost certainly been using them for a while.

In this post, I'm going to explain what promises are, how they work, where they're used and finally how to use them effectively.

Once we've got the core understanding of promises, we'll look at some more advanced functionality - chaining and resolving promises when routing.

Contents

  1. What are Promises?
  2. How do Promises Work?
  3. A Real World Example
  4. Promises - Success, Error, Then
  5. Advanced Promises - Chaining
  6. Advanced Promises - Routing
  7. The Future of Promises
  8. Wrapping Up

What are Promises?

I'm going to try and be as succinct as possible - if anyone has a shorter, clearer description, let me know!

A promise represents the eventual result of an operation. You can use a promise to specify what to do when an operation eventually succeeds or fails.

So let's see this in action. Look at the code below:

JavaScript
$http.get("/api/my/name");

This code uses the $http service to perform an HTTP GET on the URL '/api/my/name'. Let's say that this is an API we've implemented on our server that returns the name of the logged in user.

Now a common mistake for JavaScript newcomers might be to assume that the function returns the name:

JavaScript
// The WRONG way!
var name = $http.get("/api/my/name");  

It doesn't - and in fact, it can't. An HTTP request has to be executed, it'll take a while before it returns - it might not return at all if there are errors. Remember, when we make requests in JavaScript, we're using ajax which is asynchronous Javascript and XML. The keyword here is asynchronous - we return control to the browser, let it make a request and give it a function to call when the request completes.

So let's see how you actually make the request.

JavaScript
var promise = $http.get("/api/my/name");  
promise.success(function(name) {  
   console.log("Your name is: " + name);
});
promise.error(function(response, status) {  
   console.log("The request failed with response " + response + " and status code " + status);
});

Now we use the promise object to specify what to do when the request succeeds, or when it fails. Remember, the functions we pass to success or error will be called later - when this block is finished executing, we don't have the name, we've just specified what to do when we do eventually get it - or what to do if we fail to get it.

As a convenience, the success and error functions actually just return the promise, so we can simplify the code:

JavaScript
$http.get("/api/my/name")
  .success(function(name) {
    console.log("Your name is: " + name);
  })
  .error(function(response, status) {
    console.log("The request failed with response " + response + " and status code " + status);
  });

In fact, success and error are special functions added to a promise by $http - normally with promises, we just use then, which takes the success function as the first parameter and the error function as the second:

JavaScript
$http.get("/api/my/name")
  .then(
    /* success */
    function(response) {
      console.log("Your name is: " + response.data);
    },
    /* failure */
    function(error) {
      console.log("The request failed: " + error);
  });

We'll see more about the difference between success, error and then later.

That's all there is to it - a promise lets us specify what to do as a result of an operation.

How Do Promises Work?

Promises are not actually complicated, they're objects that contain a reference to functions to call when something fails or succeeds.

Under the hood, AngularJS actually wires up a promise for an HTTP request in a way a bit like this:

JavaScript
var request = new XMLHttpRequest();  
request.addEventListener("load", function() {  
  // complete the promise
}, false);
request.addEventListener("error", function() {  
  // fail the promise
}, false);
request.open("GET", "/api/my/name", true);  
request.send();

This is pseudo-code, but the idea is that it's the browser that calls us back, via the event listeners, then AngularJS can just call the appropriate method on the promise.

Now in AngularJS, the promises are created with the $q service (we'll see exactly how to do this shortly), but why $q?

The reason the service is named $q is that AngularJS' promise implementation is based on Kris Kowal's promise mechanism, which is called 'Q'. You can see the library at github.com/kristkowal/q.

This was a deliberate decision, as the Q library is widely used and well understood by the community. We're going to see a little bit later what the future of promises is in AngularJS and actually in ECMAScript 6.

A Real World Example

In this example, we'll create a service that gets the user's name, just like in our examples. However, to make it interesting, we'll set our service up so that the first time we get the name from the server, and then afterwards we'll return a cached copy.

This means we'll have to build our code to deal with the asynchronous case (the first one) and the more trivial synchronous case (getting the name from the cache).

Let's look at a pure asynchronous implementation.

JavaScript
app.factory('NameService', function($http, $q) {

  //  Create a class that represents our name service.
  function NameService() {

    //  getName returns a promise which when 
    //  fulfilled returns the name.
    self.getName = function() {
      return $http.get('/api/my/name');
    };
  }

  return new NameService();
});

Here's how it looks in a fiddle - just click 'Result' to see it working. You can click on 'Update' name to get the name, but each time it sends a request. This is what we'll change next.

Now let's update our service so that we hit the server only if we haven't already cached the name. I'll build the service blow by blow, then we can see a fiddle of it working.

JavaScript
app.factory('NameService', function($http, $q) {

  //  Create a class that represents our name service.
  function NameService() {

    var self = this;

    //  Initially the name is unknown....
    self.name = null;

So first, we create a service which is in the form of a class. It has a name field which is initially null.

JavaScript
self.getName = function() {
  //  Create a deferred operation.
  var deferred = $q.defer();

Now in the getName function, we start by creating a deferred object, using the $q service. This object contains the promise we'll return, and has some helper functions to let us build the promise.

We create a deferred object because whether we use Ajax or not, we want the consumer to use the promise - even if we can return straightaway in some circumstances (when we have the name) we can't in all - so the caller must always expect a promise.

JavaScript
if(self.name !== null) {
  deferred.resolve(self.name + " (from Cache!)");
}

If we already have the name, we can just resolve the deferred object immediately - this is the easy case. I've added 'from cache' to the name so we can see when it comes from the cache compared to the server.

Tip: You can resolve a promise even before you return it. It still works fine for the consumer.

Finally, we can handle the case if we don't already have the name:

JavaScript
else {
  //  Get the name from the server.
  $http.get('/api/my/name/')
     .success(function(name) {
       self.name = name;
       deferred.resolve(name + " (from Server!)");
     })
     .error(function(response) {
       deferred.reject(response);
     });
 }

So if we get success from the server, we can resolve the promise. Otherwise, we reject it, which means failure.

Call resolve on a deferred object to complete it successfully, call reject to fail it with an error.

Finally, we just return the promise we've built with deferred:

JavaScript
   return deferred.promise;
}

And that's it! You can see it in action below, press 'Update Name' a few times and you'll see it uses the cache.

How do we use this? We'll it's simple, here's a controller that uses the service we've built:

JavaScript
app.controller('MainController', function ($scope, NameService) {

  //  We have a name on the code, but it's initially empty...
  $scope.name = "";

  //  We have a function on the scope that can update the name.
  $scope.updateName = function() {
    NameService.getName()
      .then(
      /* success function */
      function(name) {
        $scope.name = name;
      },
      /* error function */
      function(result) {
        console.log("Failed to get the name, result is " + result); 
      });
  };
});

Now there's something different here. Before, we might have used the error or success function of the promise. But here we use then. Why is that?

success and error are functions on a promise that AngularJS adds for us when using $http or $resource. They're not standard, you won't find them on other promises.

So we've seen how promises work, what they are and so on, now we'll look into this success/error/then stuff.

Promises - Success, Error, Then

Now we know that $http returns a promise, and we know that we can call success or error on that promise. It would be sensible to think that these functions are a standard part of promise - but they're not!

When you are using a promise, the function you should call is then. then takes two parameters - a callback function for success and a callback function for failure. Taking a look at our original $http example, we can rewrite it to use this function.
So this code:

JavaScript
$http.get("/api/my/name")
  .success(function(name) {
    console.log("Your name is: " + name);
  })
  .error(function(response, status) {
    console.log("The request failed with response " + response + " and status code " + status);
  };

becomes:

JavaScript
$http.get("/api/my/name")
  .then(function(response) {
    console.log("Your name is: " + response.data);
  }, function(result) {
    console.log("The request failed: " + result);
  };

We can use success or error when using $http - it's convenient. For one thing, the error function gives us a response and status (and more) and the success function gives us the response data (rather than the full response object).

But remember that it's not a standard part of a promise. You can add your own versions of these functions to promises you build yourself if you want:

JavaScript
promise.success = function(fn) {  
    promise.then(function(response) {
      fn(response.data, response.status, response.headers, config);
    });
    return promise;
  };

promise.error = function(fn) {  
    promise.then(null, function(response) {
      fn(response.data, response.status, response.headers, config);
    });
    return promise;
  };

This is exactly how Angular does it.

So what's the advice?

Use success or error with $http promises if you want to - just remember they're not standard, and the parameters are different to those for that callbacks.

So if you change your code so that your promise is not returned from $http, as we did in the earlier example when we load data from a cache, your code will break if you expect success or error to be there.

A safe approach is to use then wherever possible.

Advanced Promises - Chaining

If you've had your fill of promises for now, you can skip to The Future of Promises or Wrapping Up.

One useful aspect of promises is that the then function returns the promise itself. This means that you can actually chain promises, to create conscise blocks of logic that are executed at the appropriate times, without lots of nesting.

Let's consider an example where we need to fetch the user's name from the backend, but we have to use separate requests to get their profile information and then their application permissions.

Here's an example:

JavaScript
var details {  
   username: null,
   profile: null,
   permissions: null
};

$http.get('/api/user/name')
  .then(function(response) {
     // Store the username, get the profile.
     details.username = response.data;
     return $http.get('/api/profile/' + details.username);
  })
  .then(function(response) {
      //  Store the profile, now get the permissions.
    details.profile = response.data;
    return $http.get('/api/security/' + details.username);
  })
  .then(function(response) {
      //  Store the permissions
    details.permissions = response.data;
    console.log("The full user details are: " + JSON.stringify(details);
  });

Now we have a series of asynchronous calls that we can coordinate without having lots of nested callbacks.

We can also greatly simplify error handling - let's see the example again, with an exception thrown in:

JavaScript
$http.get('/api/user/name')
  .then(function(response) {
     // Store the username, get the profile.
     details.username = response.data;
     return $http.get('/api/profile/' + details.username);
  })
  .then(function(response) {
      //  Store the profile, now get the permissions.
    details.profile = response.data;
    throw "Oh no! Something failed!";
  })
  .then(function(response) {
      //  Store the permissions
    details.permissions = response.data;
    console.log("The full user details are: " + JSON.stringify(details);
  })
  .catch(function(error) {
    console.log("An error occured: " + error);
  });

We can use catch(callback) - which is actually just shorthand for then(null, callback). There's even a finally - which is executed whether or not the operations fail or succeed.

Use catch and for error handling with promises - and use finally for logic that's executed after success OR failure.

The composition of promises can simplify complicated code - particularly when you add in error handling!

Advanced Promises - Routing

There's a particular area of AngularJS that uses promises to great effect, and that's the router.

Let's imagine we have a router like the following:

JavaScript
$routeProvider
   .when('/home', {
       templateUrl: 'home.html',
       controller: 'MainController'
   })
   .when('/profile', {
       templateUrl: 'profile.html',
       controller: 'ProfileController'
   })

Here, we have two routes. The home route takes us to the home page, with the MainController, and the profile route takes us to the user's profile page.

Our ProfileController uses our funky name service:

JavaScript
app.controller('ProfileController', function($scope, NameService) {  
    $scope.name = null;

    NameService.getName().then(function(name) {
        $scope.name = name;
    });
});

The problem is, until the name service gets the name from the backend, the name is null. This means if our view binds to the name, it'll flicker - first it's empty then it's set.

What we'd like to do is actually say to the router - "I'm going to go to this view, but only when you can tell me my name".

We can do this with the resolves in the router, here's how it works:

JavaScript
// Create a function that uses the NameService 
// to return the getName promise.
var getName = function(NameService) {  
        return NameService.getName();
    };

$routeProvider
   .when('/home', {
       templateUrl: '/home.html',
       controller: 'MainController'
   })
   .when('/profile', {
       templateUrl: '/profile.html',
       controller: 'ProfileController',
       /* only navigate when we've resolved these promises */
       resolve: {
           name: getName
       }
   })

So now, we have a resolve on the route - when we go to the profile page, the router will wait until the promise returned by getName resolves, then it will pass the result into the controller, as the parameter called name. Now our controller looks like this:

JavaScript
app.controller('ProfileController', function($scope, name) {

    $scope.name = name;

});

Much better! And also much more testable.

One thing you may wonder - why do I use getName as the resolve function instead of just using NameService.getName directly?

That's because the route is set up in a config function - and that function cannot have services injected. However, a resolve function can, so we just use a function and let AngularJS inject the NameService for us.

Now for an important statement:

If the first thing your controller does is fetch data from the server, it's probably wrong.

Why? Because if your controller needs data, inject it - let the router ensure the data is ready. Then you don't have controllers in an invalid state as they're loading - and your controllers become easier to test.

Be aware of resolve for routes - it's a great way to handle loading of required data, authentication and other things that you might be putting into the wrong place.

You can see the example above in action here:

What's cool is we can also see our caching logic by going to and from the Home and Profile pages. The promises are keeping our code clean and testable.

The Future of Promises

So promises are a core part of AngularJS and to use the framework effectively, you must understand how to use them and how they work. But what is the future of promises?

It's almost certain that promises are going to become a native feature of JavaScript, they are part of the proposed ECMAScript 6 specification.

The functionality of the q library and AngularJS' implementation of promises are very similar indeed to the proposed specification, but be aware that when promises become standard, AngularJS is most likely to adapt their own promises to work like native promises.

You can read more at html5rocks.com/en/tutorials/es6/promises/.

Just be aware that you'll see promises more and more, in other frameworks and in vanilla JavaScript.

Wrapping Up

I hope this post has been useful to understanding promises. Any feedback is always good, so let me know if anything is unclear or could be improved. To finish this article, here are some useful links:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.

Comments and Discussions

 
QuestionVery Nice Article, thank you! Pin
Joey Garcia25-Aug-15 17:14
Joey Garcia25-Aug-15 17:14 
QuestionAweosme Article! Pin
Member 1167197814-May-15 0:45
Member 1167197814-May-15 0:45 
GeneralMy vote of 5 Pin
Renju Vinod16-Jun-14 0:34
professionalRenju Vinod16-Jun-14 0:34 
GeneralRe: My vote of 5 Pin
Dave Kerr16-Jun-14 0:47
mentorDave Kerr16-Jun-14 0:47 
GeneralRe: My vote of 5 Pin
Renju Vinod16-Jun-14 0:51
professionalRenju Vinod16-Jun-14 0:51 
Thanks for sharing the original post
QuestionBroken Code-blocks Pin
thatraja29-May-14 3:44
professionalthatraja29-May-14 3:44 
Question5 out of 5 Pin
Eric Warren9-May-14 3:44
Eric Warren9-May-14 3:44 
AnswerRe: 5 out of 5 Pin
Dave Kerr9-May-14 4:36
mentorDave Kerr9-May-14 4:36 
GeneralMy vote of 5 Pin
Member 107925368-May-14 9:03
Member 107925368-May-14 9:03 
GeneralRe: My vote of 5 Pin
Dave Kerr8-May-14 21:30
mentorDave Kerr8-May-14 21:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.