AngularJS Services and Promises

Promises, Deferred, Futures

Call it what you will, promises are another mechanism for dealing with the asynchronous nature of some things you’ll do within JavaScript. For example, calling remote services, timers, animations completing, etc. In each case you could just have a callback, but what if you wanted to attach several callbacks to the completion or failure of a single asynchronous job? That’s difficult with callbacks, but promises make it easy.

Promises make it easy to tie together several asynchronous actions and treat them as one. This makes it easy to react only when all have completed or one of them had a problem. This is a much better solution than the nesting of callbacks you often see done in JavaScript code. Nested callbacks are often written in a way that forces requests to be made serially when they could be executed in parallel and perform better as a result.

Promises can simplify code in another way because a promise which has already resolved and one which has yet to resolve are treated are both used in exactly the same way.

Promises in AngularJS

AngularJS offers promises via a service called $q. It is modeled after a promise library called (not surprisingly) Q. You’ll see promises returned from several of AngularJS’s services, including $http (for service calls), $timeout, and $route (though I admit I’m not sure how it’s being used in the latter case).

AngularJS even has support for promises being assigned directly to $scope variables. So you can take the promise you get back from a remote service call and assign it directly to a variable knowing that when the service call finally returns, the variable will be updated and any binding attached to the variable will redisplay. That’s significantly easier than other frameworks I’ve used in the past.

Service Calls

I thought I’d give three examples of calling a service with $http and show how the promise it returns may be used directly, and examples of how caching and aggregation can be made easier thanks to promises.

For these examples I’m just calling the OpenKeyval service to store and retrieve some JSON data. It’s simple, it’s free, and I know I can count on any code I give you being able to access it without any special API key.

Source code for all of the following and more is here: https://github.com/JohnMunsch/AngularJSExamples, in particular look at the app/scripts/controllers/promises.js and app/view/promises.html to see the code specific to these examples.

Click here to see all of the following as running examples on Github

    • Example 1: Store and retrieve some values. Promises returned from $http are assigned directly to $scope variables and AngularJS dereferences them automatically.
    • Example 2: Make multiple calls to different services aggregating the results of all the calls and merging the results into a single returned value.
    • Example 3: A common pattern for services is to be able to satisfy requests from a local cache when appropriate. Promises can make that an easy upgrade for an existing service.

A Shortcoming

$q is not as full featured as Q is. That’s not surprising because AngularJS is only trying to provide as much functionality as it needs without requiring you to load up another library. AngularJS does the same kind of thing when it comes to jQuery. It has a minimal subset of functionality built in (called jQLite) that it uses if jQuery wasn’t loaded prior to loading AngularJS.

However, this is where things suddenly differ. If you do load jQuery prior to loading AngularJS then it won’t use it’s own built in version, but will instead use the more full-featured jQuery implementation. However, as far as I know, loading up the Q library or jQuery before you load up AngularJS doesn’t cause AngularJS to use Q promises or jQuery Deferred objects instead of its own subset implementation. That seems to me like an oversight or shortcoming to their implementation.

Nevertheless, I hope I’ve shown with the examples above that you can still do some really interesting things even with the limited set of functionality $q provides.

Follow Up

After a few weeks I realized that something I didn’t really cover is how assigning a promise to a variable in $scope can hinder access of that same variable in the controller. For example, if you do something like this:

$scope.someValue = $http.get(someURL);

You’ve assigned a promise to the variable and from the standpoint of code in your view, nothing really changed. You can still do {{someValue.someSubValue}} and act blissfully unaware that someValue is a promise rather than a JavaScript object. But, try the same thing from the controller code and you’ll have all kinds of problems. $scope.someValue.someSubValue isn’t anything you can access in the controller because $scope.someValue is actually a promise and will always stay a promise, even once it’s resolved. The only thing that changes once it’s resolved is that any .then() function you call on it will immediately call the closure you pass into it with the value the promise now caches. Thus you will forever more have to use it within the controller like so:

$scope.someValue.then(function (value) { $log.info(value.someSubValue); });

If you have some values you’re getting via AJAX calls or via some other mechanism that returns a promise and you need to access them as much or more from a controller as from view code then only assign the resolved value into the scope and not the promise. For example:

$http.get(someURL).then(function (value) { $scope.someValue = value; });

In that case you’re waiting until the promise resolves and only assigning the final returned value into the scope. Now both the view code and the controller can directly access $scope.someValue.someSubValue freely.

Reference

5 thoughts on “AngularJS Services and Promises

  1. micah

    Thanks for the “follow up” section – you just saved me a few more hours of headache. I saw the $scope.variable = Promise.getMe() pattern so many times in the controller I thought it was perhaps standard practice, and I was doing something else wrong.

    Reply
  2. Gush

    Thank you for this article!
    However. looking at your running examples page, it seems that only the last one is working :/

    Reply
    1. John Munsch Post author

      You’re absolutely right, Openkeyval has shut down and those examples depended upon it. I need to find a simple alternative service which doesn’t require an account for the examples. If you have any suggestions, let me know.

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s