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

The important point is:

if you chain promises or asynchronous functions using then they are executed sequentially.

Notice that to get things to happen in parallel you have to avoid chaining. For example to download the two files at the same time you would use:

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

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

or just

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

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

Notice no chaining. 

Also notice that if you want to add additional handlers to the same promise you can't use chaining. That is:

mypromise.then(onComplete1);
mypromise.then(onComplete2);

adds onComplete1 and onComplete2 to mypromise so that they we be executed when it is fulfilled. Both onComplete functions are passed the value of mypromise i.e. the same value. 

However

mypromise.then(onComplete1).then(onComplete2);

seems to do the same thing but onComplete1 is a called by mypromise with its value and onComplete2 is called by the promise returned by the then and its value is whatever onComplete1 returns.

Using Named Functions

There is a tendency in JavaScript to always use anonymous functions because they seem to fit in with the flow of control you are trying to express. Hence it is typical to write

$.get("TextFile1.txt")
  .then(
    function (value) { 
      console.log(value); 
      return $.get("TextFile2.txt");
    },

   function(error){
    handle the error
      })

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

   function(error){
    handle the error
  
} );

However there are significant down sides to using anonymous functions in particular error reporting doesn't include name of a function on the stack and there is no clue as to what the function is trying to do. Compare the above with:

$.get("TextFile1.txt")
 .then(processFirstFile,handleError1
 )
 .then(processSecondFile,handleError2)

Also notice that as you have to pass the functions as references you don't include the parameters. If you did, i.e. if you wrote:

 .then(processFirstFile(value),handleError1(error) )

then the functions would be called at once and not when the promise was settled. The parameters are in the definition of the functions not when you pass them within a call to then.

Using named functions is arguably much easier to understand and with more appropriate function names it would be even easier. Of course the cost of this is that you have to define the functions earlier in the program but this is worth it because it provides a more modular approach. 

Using named functions makes promises easier to understand.

Combining Promises

If you have a set of asynchronous tasks that occur one after the other then you need to chain together their promise objects.

However, it is more common to have a set of asynchronous tasks that you want to set running, in parallel if possible, and then do something after they have all completed. In this case what you really need to do is join or combine all the promise objects together into one promise object that is resolved only when all of the original promise objects are resolved.

You can combine promises in this way using the when method. If you specify a list of promise objects in the call to when it returns a single "master" promise object that is only resolved when all of the parameter promise objects are resolved.

The onComplete of the master promise object is passed all of the values that the parameter promise objects return.

If any of the parameter promise objects fail and are rejected then the master promise object fails. This means you could have some promise objects that are still working when the master promise object fails.

As a simple example consider downloading two files:

var myPromise1 = $.get("TextFile1.txt");
var myPromise2 = $.get("TextFile2.txt");
myTotalPromise = $.when(myPromise1, myPromise2); myTotalPromise.then(
 function (value1,value2) {
   console.log(value1)
   console.log(value2);
})

 

If you run this example you will discover that the two values returned by myPromise1 and mPromise2 are complete objects with all of the values returned by each promise - this is general behavior for the when method.

Notice also that you may well have to keep references to the promises that have been combined so that you can clean up after any tasks that fail. The general rule is that asynchronous processes are much harder to deal with when one fails than when they all work.

Note: The promise standard uses all rather than when. The big difference is that you can pass it an array of promises and it supplies an array of values to the combined onComplete function.

The when function will wait until all of the promises are resolved or at least one is rejected.

First Promise To Resolve and More!

The when function solves the problem of waiting until all of the promises in a set resolve or at least one is rejected. You can think of this is the opposite of waiting for all of the promises to resolve. For standard JavaScript promises there is a function that does this - race. It will resolve when any one of the promises resolves. The problem is that you not only get the first success you also might well get the first rejection. This isn't usually what you want. In most cases you want to try a set of alternatives and take the first that is successful not the first that completes successfully or rejects. 

An even bigger problem is that usually there is no way to abort the unfinished tasks corresponding to the promises that weren't first. 

If you do want to do anything like "first promise to return a valid result" then you can cast the jQuery promise into a JavaScript promise and use the race function. To convert a jQuery promise to a JavaScript promise all you have to do is:

var javaScriptPromise=Promise.resolve(jQueryPromise);

After this you can use the javaScriptPromise in place of the jQuery one including the race function. 

However in most cases you need something more specific to the task in hand. For example you need the first result that is successful. In this case you have no choice but to write the logic yourself.

There are lots of other possibilities. 

You could asks for a function that returns the last promise to complete successfully so you could find out how many had worked or failed. You could ask for a function that that returned the last to fail and so on the combinations are obvious. 

Rather than provide a set of functions that martial promises in this way it is better to learn how to create your own so that you can build in your own exact requirements and this is something covered in the next chapter. 

Other Promise Methods

Most of the time you can get the job done using just the then method of the promise object but jQuery does provide some extras which are mostly historical and to be avoided.

Instead of using the then method to define what should happen you can use the done, fail or always methods to add functions to the promise object which are carried out when the task is complete, fails or when the task completes or fails. 

For example the previous example could be written:

var myPromise = $.get("TextFile.txt");
myPromise.done(
 function (value) {
  console.log(value);
 });

All three functions can accept any number of functions, or an array of functions, to execute when the promise is resolved.

The big problem with the done, fail and always is that the do not chain like then. They return the first promise and this means you cannot use them to execute promises sequentially. 

So for example if you try the original sequential file download but using done rather than then:

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

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

You will find that you get the contents of TextFile1.txt displayed on the console twice not the first file followed by the second. The reason that this doesn't work is that done doesn't return a new promise but the original promise. That is in the program above myPromise1 is the same as myPromise2.

There might be special cases where done,  fail and always provide the correct behaviour but they do not work in the way you would expect from a standard promise. 

There is an argument that always is useful at the end of a chain as a default clean up function because after the final always the chain comes to an end. For this reason it isn't a function to avoid completely, however:

Avoid done and fail and use then instead. 

Also avoid using pipe which was originally provided to give the same behaviour as then and is now deprecated.

Finally the state function can be used to discover the state of a promise - pending, resolved or rejected. This isn't often useful.



Last Updated ( Thursday, 05 May 2022 )