jQuery 3 - Implementing Promises
Written by Ian Elliot   
Monday, 29 May 2017
Article Index
jQuery 3 - Implementing Promises
JavaScript Promises
Extending the Race Function

 

JavaScript Promises

Now that you have seen how Deferred works it is much easier to see how the JavaScript standard promise works. The reason we have a Deferred object, as well as a promise object, in jQuery is that we need to keep resolve and reject private and this is achieved in jQuery by making them methods of another object. In the promises standard both resolve and reject are private methods of the promise object itself.

A private method is one that is created as a closure when an object is created. This is the standard method for creating a private variable accessible from within an object but not from outside. The only difference is that the variable references a function. For example:

function myConstructor(){
 var private=0;
 this.myFunction=function(){
    alert(private);
 }
}

This is a constructor for an object with just one method, myFunction. The important part is the variable called private. This is not part of the object because it isn't declared as a property of the object. so if you try

var myObject=new myConstructor();
myObject.private=1;

you will see an error that private doesn't exist.  However, as private is in scope when myFunction is declared, it is available to it as a closure. That is:

myObject.myFunction();

does display the value of private

A private method uses the same mechanism with the small difference that the variable references a function. 

This is the mechanism that the JavaScript promise uses to make resolve and reject private methods. When you create a standard promise you use its constructor and you pass it a function that is immediately executed by the constructor. This is the function where you create the asynchronous task and then call resolve or reject accordingly. In other words this is the code that does the work.

For example the delay function example can be written using JavaScript promises as: 

function delay(t) {
 var p = new Promise(function (resolve, reject) {
  setTimeout( function () {
              resolve();
               }, t);
 });
 return p
}

You can see that it has the same basic structure the only difference is that now the code that calls the private resolve and reject functions is passed to the constructor. The constructor executes this immediately and returns the promise. 

jQuery or JavaScript Promise?

Now that you have seen the way Deferred and the JavaScript promise object work, you can appreciate that there isn't much difference. This raises the question of which one should you use?

If you are consuming promises then you can just live with whichever type of promise you are returned. For example, if you use jQuery's JSONget then it returns a jQuery promise that you can use as if it was a JavaScript promise. 

On the other hand, if you are adding promise support to a function then it makes sense to use JavaScript promises as this is the way of the future and your new promisified function will still work with jQuery.

The only reason to prefer jQuery's Deferred is if you want to support browsers that don't have the latest version of JavaScript and that mainly comes down to any version of IE. jQuery 3 supports IE 9+ and if you try the delay function out in both versions you will discover that the jQuery version works on IE 11, say, but the JavaScript promise version fails with a Promise object not defined error.

Given that IE 11 still has a 4% market share  at the time of writing, you might want to consider using jQuery Deferred. When IE has a small enough market share to ignore, converting to using JavaScript promises would be an easy task. 

The Then Parameter Problem 

As delay now returns a promise, it seems obvious that it could be used in a chain (see the previous chapter). However, there is a problem.

If you try:

getTime();
delay(1000)
  .then(getTime)
   .then(delay(1000))
    .then(getTime);

where getTime is something like:

function getTime() {
 var time = new Date().getTime();
 console.log(time);
}

which shows a timer count in milliseconds, what you discover is that it appears to work, but if you look carefully the getTime functions report times that are only a few milliseconds apart, rather than 1000ms apart. 

The reason should be easy to spot. The functions that are being passed to the then function have parameters and this means they are evaluated at once and not passed to the then to be activated at a later time. 

The problem is that you cannot pass a parameter to a function that you use in a then

Notice that when it is called by the promise, the function may be passed any number of parameters depending on the way the promise is settled. 

There are a number of solutions to the problem, but none are 100% satisfactory. 

The first, and most obvious, is not to use a parameter at all, but this would result in a delay function that gave a fixed time delay and this generally isn't what you want. 

The second is to use a technique from functional programming called "currying" to reduce the number of parameters in the function. For example:

getTime();
delay(1000)
  .then(getTime)
  .then(function(){return delay(1000);})
  .then(getTime);

In this case we have used the anonymous function to curry the delay function, i.e. we have reduced the number of parameters to zero. If you try this you will find that it works and each of the times is roughly 1000ms apart. 

You can take this one-off currying and create a function that will automatically curry delay for you, for example:

function delay(t) {
  return function () {
     var d = $.Deferred();
     setTimeout( function () {
         d.resolve(0);
     }, t);
     return d.promise();
   };
}

You can see that this is the same idea, but now the delay function returns a function that delays for t milliseconds with no parameters. With this version of delay you can use:

getTime(); 
delay(1000)()
  .then(getTime) 
   .then(delay(1000)) 
    .then(getTime);

The extra parentheses following the first use of delay are not a misprint. The delay function returns a function that delays for t milliseconds and to implement the delay it has to be called. 

The need for the double pairs of parentheses is not nice, but there seems to be no way that a function that returns a promise and accepts parameters can be used in the same way outside and inside a then

The final way of doing the job is to use bind to curry the delay function. The bind function returns another function with a specified context and fixed values for any of its parameters. Using the original delay function we can call it in a then using:

 getTime();
 delay(1000)
  .then(getTime)
   .then( delay.bind(null,1000))
    .then(getTime);

The bind returns a function with the call context set to null and the first parameter set to 1000. The call to bind is reputed to be slow.

Of the solutions, probably the best is to write the function using a parameter and remember to wrap it in an anonymous currying function if you use it in a then:

 .then(function(){return delay(1000);})

This is one of the negative features of using promises. You have to remember that a function that returns a promise can have parameters, but you cannot specify these parameters when you use the function in a then unless you use currying or something similar. 

Composing Promises

One of the more advanced aspects of using promises is writing functions that work with multiple promises in various ways. Many promise libraries provide utilities such as any which is fulfilled if any promise is, some, fulfilled if a fixed number of promises are and so on. The JavaScript promise standard provides just two

  • all fulfilled if all are 
  • race fulfilled if one is

The jQuery Deferred only provides all

Rather than providing lots of different standard promise-composing functions, it is simpler to learn how to write your own that do exactly what you want. Usually the problem is how to handle rejects, and this you can tailor to the situation. 

As an example, let's implement a simple version of the race function which returns a promise that resolves when the first of two promises resolve. It is always a good idea to try to implement an idea as simply as possible and then extend it to more realistic examples. It turns out that race is very easy to write: 

function race(p1,p2){
 var d = $.Deferred();
 p1.then(function(value){d.resolve(value);},
         function(error){d.reject(error);}
        );
 p2.then(function(value){d.resolve(value);},
         function(error){d.reject(error); }
        );
 return d.promise();
}

All that happens is that we return a new promise object that is resolved or rejected when any of the promises provided as arguments resolves or rejects. Notice that we don't do anything to stop the other promises from completing. It is generally difficult to cancel a pending asynchronous operation. Also notice that as a promise is immutable we don't need to worry about later promises settling and trying to set the returned promise to something different. 

This can be used to get the first promise to resolve or reject:

getTime();
race([delay(1000), delay(2000)]).then(getTime);

In this case you will see a delay of 1000ms. 



Last Updated ( Thursday, 05 May 2022 )