jQuery 3 - Consuming Promises
Written by Ian Elliot   
Thursday, 06 July 2017
Article Index
jQuery 3 - Consuming Promises
Chaining Promises
Combining Promises
Promise Error Handling

justjquery 

The Ajax Get With a Promise

An example of using a promise in a function that is potentially slow is provided by the jQuery Ajax get method, but in fact any of the Ajax functions work in the same sort of way.

The get method will return any file as specified by the URL you provide. Of course, it could take some time to return the file as it has to be downloaded. This is a perfect use of the promise object and the get method returns a promise.

So. for example. if you want to download the file you might use:

$.get("TextFile.txt");

which would start the file downloading using another thread of execution and return immediately.

How do you get the contents of the file when the task completes?

The early version of jQuery didn't use promises and you had to supply call-back functions within the get method. Since jQuery 1.5 all of the Ajax functions have returned a promise object, which is much easier to use.

To download a file you now simply have to keep a reference to the returned promise object:

var myPromise = $.get("TextFile.txt");

and make use of its then method to process the returned data:

myPromise.then(function (value) {
                  console.log(value);
               });

You could also define functions to handle an error or report the progress of the download. In this case the anonymous function is called when the file has completed its download and always after the code that created the promise has exited.

The first value returned by the promise is the contents of the file as a string.

One of the problems with trying to find out what functions return as their final result is that usually the documentation just gives the fact they return a promise object with no mention of what the values are.

In jQuery what is passed to an onComplete, onError or onProgress function is determined by the asynchronous function that returns the promise object. It can return multiple values. In the case of the Ajax methods it returns a set of values corresponding to the properties of the jqXHR object in the order they are documented.

A JavaScript standard promise always only returns a single value a jQuery promise can return multiple values. 

Also, although you can write the code as above with the then method called later, it is usual to not store the promise object and simply call the then method as in:

$.get("TextFile.txt").then(
 function (value) {
  console.log(value);
 });

It is also worth remembering that the functions defined within a then method are closures and so have access to the variables that are in scope at the point at which the function is defined.

In fact the get method doesn't return a promise object but a mix-in, a jqXHR object, that is part the original object that get used to return and part a promise object. This allows more flexibility than just returning a promise object and it is one of the strengths of JavaScript's approach to object-oriented programming - but it can be confusing.

justjquery

Chaining Promises 

At the moment it looks as if a promise is just another way to write callbacks. After all what is the difference between writing

slowFun(args,sucessCallback,errorCallback);

and

slowFun(args).then(sucessCallbak,errorCallback);

Looked at in this way there is no real difference and if it helps you understand the way promises work then by all means think about then as just another way to write callbacks. However there is a little more sophistication here that you might think and its all to do with the idiom of chaining functions.

A particular problem with asynchronous tasks is to arrange that taskB is only started when taskA has completed. In chapter 3 we looked at various ways of achieving this including the use of jQuery's function queue. Promises provide yet another and arguably the best way of doing this.

If you want to perform two asynchronous tasks, one after the other, you can by simply chaining then method calls. As in all function chaining to make it work you have to return the correct sort of object from each function and in this case its another promise.

In many ways this idea is key to understanding and using promises in more sophisticated ways.

To understand what is going on we need to master one subtle point of the way promises work.

The then method always immediately returns a promise object.

Note: This is not always the way then used to work in jQuery. The then function was implemented in a nonstandard way and this was the source of the criticism of jQuery's promises. In jQuery 3 then works correctly and this is what is described here. It isn't worth learning the wrong way to do this so if you have to cope with legacy code check the documentations. 

You can think of this as a default object returned to make sure you can always chain calls on the then function. This default promise has as its value  the value returned by the onComplete function of the original promise object.

For example:

var myPromise1 = $.get("TextFile.txt");

var myPromise2=myPromise1.then(
       function (value) {
          console.log(value);
          return "Hello";
       });

myPromise2.then(
       function (value) {
          console.log(value);
       });

The get function returns a promise object which we can use to set an onComplete using then. However then also returns a promise object which can have an onComplete which will be called when it is resolved. The value of the promise is whatever the first onComplete returns. 

If you run this you will see  the contents of the file followed by "Hello" displayed in the console.

It is important that you really understand this example.

The key is to realize that myPromise1 and myPromise2 are returned at once by the get and the then. This is always the case because it is what allows the code to continue executing and run to completion.

At some time in the future after the code has completed the file finishes loading and myPromise1 is fulfilled and the onComplete function is fired with the promise's value i.e. the contents of the text file. It then returns "Hello" which is the value that myPromise2 has been waiting for and it and calls its onComplete function. 

Of course this isn't how this code would generally be written. You can write it using the chaining or fluent style:

$.get("TextFile.txt");
      .then(
            function (value) {
              console.log(value); 
              return "Hello"; 
           })
     .then(
           function (value) { 
              console.log(value); 
           });

This is neater and more compact but it is harder to see what promise object are in play.  

What is the advantage of then returning a promise? 

To see the advantage you have to think what if the first onComplete function was another slow operation?

In this case it should return a promise object so that the UI thread could get on with other things and not be held up by the onComplete function.

The rule is that if an onComplete function returns a value then it is the promise's value but if it returns a promise then that promise's value if forwarded to the default promise as its value. 

That is:

  • If a promise is fulfilled by a normal i.e. non-promise value then the promise is fulfilled.
  • If a promise is fulfilled by another promise then it isn't fulfilled until the second promise is and so on.. 

 

For example, suppose we need to download two files one after the other, we might use:

var myPromise1 = $.get("TextFile1.txt");
var myPromise2= myPromise1
  .then(
       function (value) {
         console.log(value);
         return $.get("TextFile2.txt");
       });

myPromise2.then(
       function (value) {
         console.log(value);
       })

What happens is almost identical to the previous example.

The get immediately returns myPromise1 and starts to download the file. Next the then immediately returns myPromise2 and sets up the onComplete callback.

The code then comes to an end freeing up the UI thread. At some time later the file finishes loading and this resolves myPromise1 with a value equal to the contents of the file. This causes the onComplete to start running which prints the contents of the file and starts the second download working.

The second get immediately returns a promise which is pending i.e unresolved and starts to download the second file. MyPromise2 continues to wait because the promise it has been supplied with is unresolved. MyPromise2 can only resolve if the supplied promise is resolved they are both waiting on the value to become available.

Eventually the second file is downloaded and the promise returned by the get is resolved and this provides a value for MyPromise2 and it too is resolved. Finally MyPromise2's onComplete is called and displays the value i.e. the content of the second file. 

What happens when a promise is returned a promise is the trickiest understanding promises. But if you think of a simple promise as waiting for a value to be available then a promise that is waiting for a promise is waiting for the same final value:

promise1 -waiting> promise2 -waiting> value

when promise 2 gets the value it resolves and passes the value back to promise 1 which is also resolved. The principle extends to more than two promises and you can have a chain of promises all waiting for the final one in the chain to resolve. 

Of course, you might well want to download both files at the same time, but if you care about the order of downloading and need the first file to finish downloading before the second even starts, this is the way to do it.

Without using promises the only way to make sure that asynchronous functions occur one after another is to nest callbacks or you could use a function queue as described in chapter 3. 

Chaining promises is a fundamental way to order asynchronous tasks.

This is to be preferred to nested promises which is the promise analog of nesting callbacks e.g.:

var myPromise = $.get("TextFile1.txt");
myPromise.then(
   function (value) {
            console.log(value);
            $.get("TextFile2.txt").then(
                 function (value) {
                      console.log(value);
                  })
    })

 

This nests a callback within a callback. It is better to return the promise object and write things like:

 object.then(oncomplete1).then(oncomplete2)

and so on.



Last Updated ( Thursday, 05 May 2022 )