Technouz

Resolving Multiple Promises In AngularJS Using $q Service

By Zahid Mahmood / 16 November 2016

Wrapping your head around the concept of promises in JavaScript, particularly AngularJS can be quite difficult. Especially if you are new to asynchronous programming. But, once you get the hang of it, it becomes difficult to unlearn and you unleash yourself in to a new way of programming much more complex and sophisticated web applications.

What is Asynchronous Programming

In the most simplest of words, Asynchronous Programming is parallel programming. It allows you to execute blocks of code at the same time. In most cases, calling a function blocks of any proceeding operations until the initial function is complete.

Let's take an example:

getThePageHeader();
getThePageBody();
changeBackgroundColor();

In the above example, if we were to use traditional synchronous programming, our application would load the page header, followed by the body and then execute the changeBackgroundColour() function. This means that whilst the page header is loading, the page body is not loaded at all.

Why is this a problem? Let's say for some reason, the page header takes five seconds to load but the page body only takes one second. On a traditional website application, the user may become frustrated having to wait six seconds to render the content they want to view. Using asynchronous programming, we can load the page header and page body at the same time, meaning the application user will only need to wait a total of five seconds for the entire page to render - but more importantly, the body where the important content is, will load within one second. So in visual terms, the user will see the body of the page before the entire header is loaded. Since the body of a page usually contains the content a user is after - and the header only has things like the logo and navigation menu - allowing faster rendering of the body will lead to a much improved User Experience.

Promises

When talking about JavaScript, a promise is literally a representation of an eventual result. Think of it as a placeholder into which the successful result will materialise. If, for example, you use the $http.get() call to a URL, it will return a promise to give you the content of that webpage - not the actual content of the webpage. To get the content of a URL from a promise, it needs to be resolved. And in some cases this can take a while; that's why a promise is like a placeholder. It remains unresolved until the content is actually loaded. In AngularJS, resolving a promise is typically done by chaining a .then() function to the promise.

In AngularJS we can use the concept of promises to take advantage of asynchronous programming in our applications.

A promise in Angular essentially says: Load X and then do Y. But whilst X is loading, proceed to load Z and every other block of code that is not waiting for the promise to be resolved.

A simple $http Angular Promise

The easiest, built-in promise with AngularJS is probably the $http.get() call. It takes a string argument for the URL you want to HTTP GET and returns a promise. In our test application, we can do the following to experiment:

var htmlContent; var promise = $http.get('https://www.technouz.com');

promise.then(function(response) { htmlContent = response.data; });

// anything here will be executed at the same time as the 'promise.then()' block above

The above example of code shows how we can make a HTTP GET call to the Technouz homepage. When the call has been made, and is successful, we can store the response data (HTML code for the homepage) in a variable named htmlContent.

If we were to try and print the value of htmlContent (using console.log()) right after the promise.then() call, there is a chance it may still be empty. This largely depends on how long it takes to complete the HTTP call, but the key idea here is that code not within the .then() block is executed immediately, and in parallel, with the promise resolve.

The AngularJS $q Service

The AngularJS $q service provides us with a way of processing promises. One of it's features is that it allows us to wrap custom code in a promise, and execute it in the manner described above.

The AngularJS documentation for the $q service is actually very thorough and worth a read to understand how to fully utilise it.

We can take the $q.when() function, and pass it two arguments. The first argument is the promise, and the second argument is the success callback function. If a third argument is to be passed, it should be a function which is called upon a rejected promise.

So, by way of example, the following extract of code will call a function which returns a promise, process the promise and then assign the data to a local variable:

$q.when(getDemoInformation(), processDemoInformation(response));

function getDemoInformation() { return $http.get('https://www.technouz.com'); }

function processDemoInformation(response) { var temp = response.data; }

Multiple Promises with AngularJS $q service

Now we get to the good stuff. So how do we deal with multiple promises?

Let's say we wanted to get data from three HTTP endpoints, and only call the function to display the data once all three data-sources had been loaded. The conventional idea would be to call three functions that return a promise each and resolve each promise. The problem here would be that the application make not necessarily wait for all three promises to be processed and resolved before continuing. For example, if the first function takes five seconds to execute, the second takes two seconds and the final function takes one second; the call to displaying the data may occur before the first promise has completely resolved. In other words, the call to display the data may only have data from the first and second functions available to process.

Thankfully, there is a way around this.

We can take advantage of the $q.all() which can be used to wait for an array of promises to be resolved, before calling the display function. This means that we need to store our promises in an array and then process the promises using $q.all().

function getsArrayAndAssigns() { var object = GetValues(); }

function getValues() {
  var promiseList = []; // declare an array to store the promises
  promiseList.push(returnsPromiseA()); // add the promise to the array
  promiseList.push(returnsPromiseB());
  promiseList.push(returnsPromiseC());

// promiseList now is an array of length=3 and contains promises

return $q.all(promiseList).then(function (response) {
    var arrayOfDetails = { name: response.data\[0\].name, age: response.data\[1\].age, colour: response.data\[2\].colour };
    return arrayOfDetails;
}).catch(function (error) {
    console.log("it failed....");
});

}

function returnsPromiseA() { return $q.when($http.get("https://www.technouz.com/promise-a")); }

function returnsPromiseB() { return $q.when($http.get("https://www.technouz.com/promise-b")); }

function returnsPromiseC() { return $q.when($http.get("https://www.technouz.com/promise-c")); }

The code above can be used as a template for any application which requires multiple promises to be resolved before continuing. The code above gives a good foundation for three very basic HTTP calls when return a promise each. The getValues() function simply adds each of these promises to an array, and then processes the array containing promises using $q.all(). The promises are then resolved in the order of the array (starting from index 0 and increment to the last index of the array). Once all the promises have been resolved, the chained .then() function is called to process the response values passed back.

One thing to note is that the response argument passed to the callback function will contain an array of data objects. The size of that array will depend on the number of successful promise resolves.

Thanks for reading!

My name is Zahid Mahmood, and I'm one of the founders of Anterior. I started this technology blog when I was in high school and grew it to over 100,000 readers before becoming occupied with other projects. I've recently started writing again and will be posting more frequently.