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

Promises are still a new feature in JavaScript.  This leads on to the need to promisify existing and future code. To do this you need to know a little about how promises work internally and how to make them do what you want.

Just jQuery
Events, Async & AJAX

Is now available as a print book: Amazon 

jquery2cover

 

Contents

  1. Events, Async & Ajax (Book Only)
  2. Reinventing Events
  3. Working With Events
  4. Asynchronous Code
  5. Consuming Promises
  6. Using Promises 
  7. WebWorkers
  8. Ajax the Basics - get
  9. Ajax the Basics -  post
  10. Ajax - Advanced Ajax To The Server
  11. Ajax - Advanced Ajax To The Client
  12. Ajax - Advanced Ajax Transports And JSONP
  13. Ajax - Advanced Ajax The jsXHR Object
  14. Ajax - Advanced Ajax Character Coding And Encoding 

Also Available:

buy from Amazon

smallcoverjQuery

 

Advanced Attributes

 

After learning how to use or consume promises the next step is add support for promises to asynchronous functions and how to work with them to create new promise features.

The jQuery Deferred Object

The big problem initially in implementing promises was that it was essential to provide functions so that the code that was using the promise to signal when it was finished could change the state of the promise, but code that was consuming the promise using then and that catch function was unable to change the promise's state.

That is:

only the code that created the promise should be able to set the promise's state. 

The earliest solution to this problem of keeping the internal state private was to use a two-object solution. A deferred object was used by the promise creator to manage the promise. The deferred had the necessary functions to resolve and reject the promise and the promise had all of the functions the consumer needed, like then. In practice it was better to have the deferred object also having all of the functions that the promise had and so the deferred looked like a "super" promise object. In retrospect this is probably a mistake as it results in a confusion between what the deferred is and what it is used for. If you wanted to you could pass the deferred object to the user rather than the promise and this would allow them to change the internal state. 

To summarize:

  • The deferred object has all of the properties and methods of a promise and some additional methods that allow you to change the state of the promise associated with it.

  • The deferred object is used by the code that creates and manipulates the promise.

  • That code should return only the associated promise to its clients so that they can use the then and similar methods but not affect the promise's state. 

You create a deferred using:

var def=$.Deferred();

The most important deferred methods are:

resolve/reject

resolveWith/rejectWith

In most cases you will use resolve or reject.  

If you call a deferred resolve method then the promise associated with it is fulfilled. That is, any onComplete handler attached using then will be called with the same arguments. 

For example

def.resolve(myValue);

sets the promises state to fulfilled which results in any then onComplete handler to be called with myValue as a parameter.

The reject method works in the same way but it causes the promise to be rejected. 

For example:

def.reject(myReason);

sets the promise's state to rejected and any failure callback is called with myReason as a parameter. 

Notice that both the resolve and reject methods are different from JavaScript standard promises which only allow a single argument to be passed. If you want to be compatible with the promise standard than only pass a single parameter in resolve or reject. If you have multiple values you need to pass then create an object with them as properties and pass the single object. 

The functions resolveWith and rejectWith work in the same way as resolve and reject, but the first argument sets the context, i.e. this in the calls of the onSucess or onReject handlers. So for example:

def.resolveWith(myButton,myValue);

will set the promise's state to fulfilled and call any onSucess handler with myValue as a parameter and with this set to myButton. It isn't often that you need resolveWith or rejectWith, but they can make using object methods as promise handlers possible. 

The only other deferred function that is important is promise which returns a reference to the promise that the deferred is associated with. 

Using A Deferred

One of the problems of using Deferreds is seeing how they are actually used to implement an asynchronous function that returns and manages a promise. The best way of finding out is by way of a simple example. 

The timeOut function has already been introduced in earlier chapters as a way of turning a synchronous function into an asynchronous one. It can also be used to delay the running of a function by a specified time:

timeOut(function,time);

will run the specified function after time milliseconds have past. We can turn this into a delay function that returns a promise that resolves after the specified time. 

All we have to do is create a deferred:

function delay(t) {
    var d = $.Deferred();

Next we start the asynchronous operation off and arrange that when it is finished it sets the Deferred's state to fulfilled or rejected. In this case it is difficult to see how the timer could fail and so we just need to fulfill the Deferred when the time is up:

setTimeout(function () {
                d.resolve(); }, t);

Now all we have to do is return the promise so that the client code can use it:

return d.promise();
}

That's it!

The complete function is:

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

If this is your first promise-returning function you might think it is strange - where is the function that is run when the delay is up?

The whole point is that this function will delay running any function the client code associates with it using then and it will run multiple functions if you want it to. For example, to use it you might write:

delay(1000).then( function () {
                    console.log("time up");
                  }
);

As delay returns a promise, you can use any of the usual methods to schedule other functions depending on its state - primarily then and catch, but this is trickier than it might seem, as described later.



Last Updated ( Thursday, 05 May 2022 )