The Deferred methods

In jQuery, a Deferred object is created by calling the $.Deferred() constructor. The syntax of this function is as follows.

Method syntax: $.Deferred
$.Deferred([beforeStart])
A constructor function that returns a chainable utility object with methods to register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function. It accepts an optional function to execute before the constructor returns.
Parameters
beforeStart(Function) A function that’s called before the constructor returns. The function accepts a Deferred object used as the context (this) of the function.
Returns
The Deferred object.

The Deferred object allows for chaining, like many of the jQuery methods we’ve discussed so far, but it has its own methods. You’ll never find yourself writing a statement like this:

$.Deferred().html('Promises are great!');

Having created a new Deferred object isn’t of any utility if you don’t know how to use it. Starting from the next section, we’ll cover the methods exposed by this object.

Resolving or rejecting a Deferred

One of the first concepts we discussed in this lesson is the state in which a promise can be. In jQuery, a promise can be resolved (the promise is successful), rejected (an error occurred), or pending (the promise is neither resolved nor rejected). These states can be reached in two ways. The first is determined by code that you or another developer has written and with an explicit call to methods like deferred.resolve()deferred.resolveWith()deferred.reject(), or deferred.rejectWith(). These methods, as we’ll discuss in a few moments, allow you to either resolve or reject a promise. The second is determined by the success or the failure of a jQuery function—for example, $.ajax()—so you don’t have to call any of the previously mentioned methods yourself.

Before you can write any code that uses the Deferred object, you have to get acquainted with these methods, so let’s discover their syntax. The syntax of the deferred.resolve() method is shown here.

Method syntax: deferred.resolve
deferred.resolve([argument, …, argument])
Resolve a Deferred triggering the execution of any successful callback defined, passing any given argument. This method accepts an arbitrary number of arguments.
Parameters
argument(Any) An optional argument of any type that is passed to the success callback functions defined.
Returns
The Deferred object.

The syntax of the deferred.resolveWith() method is shown here.

Method syntax: deferred.resolveWith
deferred.resolveWith(context[, argument, …, argument])
Resolve a Deferred triggering the execution of any successful callback defined, passing any given argument, and setting context as their context.
Parameters
context(Object) The object to set as the context of the successful callbacks.
argument(Any) An optional argument of any type that is passed to the success callback function defined.
Returns
The Deferred object.

Sometimes it happens that a promise needs to be rejected. For such circumstances you can employ the deferred.reject() method. Its syntax is shown here.

Method syntax: deferred.reject
deferred.reject([argument, …, argument])
Reject a Deferred triggering the execution of any failure callback defined, passing any given argument. This method accepts an arbitrary number of arguments.
Parameters
argument(Any) An optional argument of any type that is passed to the failure callback function defined.
Returns
The Deferred object.

In the same way that jQuery defines a method to set the context for the successful callbacks, you can set it for failure callbacks by using the deferred.rejectWith() method. The syntax of this method is reported here.

Method syntax: deferred.rejectWith
deferred.rejectWith(context[, argument, …, argument])
Reject a Deferred, triggering the execution of any failure callback defined, passing any given argument, and setting context as their context.
Parameters
context(Object) The object to set as the context of the failure callbacks.
argument(Any) An optional argument of any type that is passed to the failure callback function defined.
Returns
The Deferred object.

Up to this point you’ve learned how to create a Deferred and how to resolve or reject it, but the fun comes when you write code to react to the change in a Deferred’s state. Let’s see how.

Execute functions upon resolution or rejection

Usually you want to know when a Deferred is resolved to perform some actions. To do that, you can employ the deferred.done() method. It accepts one or more arguments, all of which can be either a single function or an array of functions. These callback functions are executed in the order in which they were added. The syntax of this method is shown here.

Method syntax: deferred.done
deferred.done(callbacks[, callbacks, …, callbacks])
Add handlers that are called when the Deferred object is resolved. This method accepts an arbitrary number of callbacks with a minimum of one.
Parameters
callbacks(Function|Array) A function or array of functions that is called when the Deferred is resolved.
Returns
The Deferred object.

In the same way that you can execute operations when a Deferred object is resolved, you can run functions when it’s rejected. To do that, you can use the deferred .fail() method. Its syntax is as follows.

Method syntax: deferred.fail
deferred.fail(callbacks[, callbacks, …, callbacks])
Add handlers that are called when the Deferred object is rejected. This method accepts an arbitrary number of callbacks with a minimum of one.
Parameters
callbacks(Function|Array) A function or array of functions that is called when the Deferred is rejected.
Returns
The Deferred object.

In this case, too, when the Deferred is rejected, the callbacks are executed in the order in which they were added.

Now that we’ve introduced you to these methods, we’re able to show you some code to demonstrate what this fuss is all about. To start simply, we’ll modify the example we discussed at the beginning of this lesson and rewrite it to use Deferreds. This time we want to provide you with a working demo, so we’ll add in a simple PHP page, called “integer.php,” to simulate the Randomizer service. The resulting code is shown in the next listing and is also available in the file lesson-13/randomizer.1.html.

Listing 13.2. Using Promises with Ajax requests, version 1

The code in this listing is called version 1 because you’ll work on it again and refactor it until you reach a clean and elegant solution to this problem using promises. As you can see, it isn’t much different from listing 13.1, but it still shows some important concepts.

The first concept is that you store the jqXHR object returned by the $.ajax() function in two variables called promise1  and promise2 . As we mentioned, a jqXHR object is Promise-compatible, which means that you can call methods like done() and fail() on it. Using variables wasn’t really necessary because you could have chained the done() and fail() methods directly, but we wanted to make clear to you what kind of object is returned by $.ajax() through the name of the variables.

Then you attach the same success and failure callbacks that were developed in listing 13.1. The callbacks to execute if the Ajax request is successful are added by calling the done() method on the variables promise1  and promise2 . The same thing happens to the failure callbacks that are added by calling the method fail() on promise1  and promise2 .

While we’re talking about done() and fail(), we want to highlight that if you add a success or failure callback after the state of a Deferred is changed to either resolved or rejected, the callback will be executed immediately. Consider the following code:

var promise1 = $.ajax('integer.php');
setTimeout(function() {
   promise1.done(function(data, status, jqXHR) {
      // Code here
   })
   .fail(function() {
      // Code here
   });
}, 5000);

In this case you delay the statement that adds the callbacks by five seconds to simulate a long enough time for the “integer.php” page to be executed and its response returned (this isn’t guaranteed, but it’s enough for the sake of the example). Based on this assumption, at the time done() and fail() are invoked to add the callbacks, the state of promise1 is already defined. What you might expect, because of your experience with listeners added to events, is that none of them will be executed. The reason is that the “event” of changing the state of the promise has already happened. But one of the two functions will still run, which is an important difference.

Another interesting difference is that you can add as many callbacks as you like with one statement. Let’s say that if an Ajax request is successful, you want to execute two functions, foo() and bar(). In a traditional approach, you’d write code like the following:

$.ajax('integer.php', {
   success: function(data, status, jqXHR) {
      foo(data, status, jqXHR);
      bar(data, status, jqXHR);
   }
);

Using the done() method, you can rewrite it in a single line of code:

$.ajax('integer.php').done(foo, bar);

Or equivalently (note that here you pass an array of functions):

$.ajax('integer.php').done([foo, bar]);

Much better, isn’t it?

Listing 13.2 employs some of the new methods you’ve learned, but it still suffers from the awkward synchronization approach used. Let’s see how to do better.

The when() method

In order to edit listing 13.2 to its final version, we need to introduce you to another utility function: $.when(). It provides a simple way to execute callback functions based on one or more objects, usually Deferred or Promise-compatible objects representing asynchronous events. This is exactly what you need in your code because you have two Promise-compatible objects returned by the two $.ajax() calls. But before using it, let’s discover its syntax and the parameters it accepts.

Method syntax: $.when
$.when(object[, object, …, object])
Provides a way to execute callback functions based on one or more objects, usually Deferred or Promise-compatible objects representing asynchronous events. This function accepts an arbitrary number of objects with a minimum of one.
Parameters
object(Deferred|Promise|Object) A Deferred, Promise, Promise-compatible, or JavaScript object. If a JavaScript object is passed, it’s treated as a resolved Deferred.
Returns
A Promise object.

The $.when() utility function has an interesting point to highlight: it doesn’t return a Deferred object but a Promise object. What’s returned by this method can’t be resolved or rejected; you can only call done()fail(), and a few other methods on it. It’s worth noting that in ECMAScript 2015 there’s a similar method called Promise.all().

If you pass a single Deferred object to $.when(), its Promise object is returned; otherwise a new Promise is created starting from a “master” Deferred that will keep track of the state of all the objects (PromisePromise-compatible, Deferred objects, and so on) passed to $.when().

$.when() causes the execution of the success callbacks (the functions to run in the event of success) when and if all the objects passed to this utility function are resolved (in the case of Deferreds, Promises, and Promise-compatible objects) or can be considered resolved (any other type of objects). Conversely, it causes the execution of the failure callbacks as soon as one of the Deferreds is rejected or one of the Promise or Promise-compatible objects is in a rejected state. The arguments passed to the callback functions, whether for success or failure, are the ones passed to either resolve() or reject(), depending on the case.

Before we lose you in this sea of information, let’s see a concrete example. As we mentioned earlier, we’ll rewrite the code of listing 13.2 and try to improve it by using Deferreds. The final result is shown in the next listing and is also available in the file lesson-13/randomizer.2.html.

Listing 13.3. Using Promises with Ajax requests, version 2

Looking at this code should make you feel happy and your eyes should shine. Thanks to $.when(), we’ve highly simplified the code of listing 13.2 and made it more readable and manageable. Let’s analyze the code.

The key point is the use of $.when(), which you employ to solve the issue you had with the synchronization of the results of the Ajax requests . By doing so, you don’t have to set the success and failure functions for each of them. In fact, you’ll only set them on the Promise object returned by $.when() through the use of done()  and fail() . Once again, we want you to remember that a Promise object is created starting from a Deferred object or a jQuery object (in this case it’s created internally by $.when()) and possesses a subset of the methods of the Deferred (always()done()fail()state(), and then()).

The success() function is executed when both of the Promise-compatible objects are fulfilled. Its behavior isn’t changed compared to the previous version, but there is an interesting detail to discuss. You define two parameters for this function, params1 and params2, because this is the number of the Promise-compatible objects you’re using . Each of these parameters is an array containing the usual parameters passed to the success callback of a $.ajax()$.get(), or $.post() function: datastatusText, and jqXHR. It’s worth noting that if the value passed was a single object, it wouldn’t be wrapped.

The last function defined in the listing is fail() . It’s extracted from the previous listing’s handler() function and it’s executed as soon as one of the Ajax requests fails.

In addition to resolving or rejecting a Deferred, you can also give a notification about the progress of the process.

Notifying about the progress of a Deferred

Sometimes you may have some code that needs to know the state of a Deferred. For example, if you’re retrieving data asynchronously, you want to know the percentage of completion of the process. If this process is promise-based, you may have a function waiting for either the resolution or the rejection of that promise, which you want to keep informed about its state. For such occasions, you can employ deferred.notify(). The syntax of this method is as follows.

Method syntax: deferred.notify
deferred.notify([argument, …, argument])
Triggers the execution of any progress callback defined, passing any given argument. This method accepts an arbitrary number of arguments.
Parameters
argument(Any) An optional argument of any type that is passed to the progress callback functions defined.
Returns
The Deferred object.

In case you want to force the context of the callback functions executed, you can use deferred.notifyWith().

Method syntax: deferred.notifyWith
deferred.notifyWith(context[, argument, …, argument])
Triggers the execution of any progress callback defined, passing any given argument and setting context as their context
Parameters
context(Object) The object to set as the context of the progress callbacks
argument(Any) An optional argument of any type that is passed to the progress callback functions defined
Returns
The Deferred object

Thanks to these methods, you’re now ready to see how you can perform some actions while an operation is in progress.

Follow the progress

With the methods discussed in the previous section, you can be notified about the progress of an asynchronous operation. But this is completely useless if you can’t “listen” for these updates. Enter the deferred.progress() method.

Method syntax: deferred.progress
deferred.progress(callbacks[, callbacks, …, callbacks])
Add handlers that are called when the Deferred object generates progress notifications. This method accepts an arbitrary number of callbacks with a minimum of one.
Parameters
callbacks(Function|Array) A function or array of functions that is called when the Deferred object generates progress notifications.
Returns
The Deferred object.

Now that you know how this method works, let’s fix the idea with an example. Let’s say that you want to create an animation for a progress bar and you want to be able to keep track of the progress of the animation to display the percentage of completion. To do that you can use the deferred.progress() and the deferred.notify() methods.

Note

The example we’re going to develop isn’t ideal. A better way would be to return the Promise object of the Deferred used and let the caller of the animation function update the percentage. We’ll modify it in the next section, but for the moment we want to keep things as simple as possible and move in little steps, so bear with us.

The code that implements the previous requirements is reported in the following listing and available as a JS Bin. You can find it in the file lesson-13/deferred.progress.1.html.

Listing 13.4. Using deferred.progress(), version 1

In this example you create a simple progress bar that shows the percentage of completion . Inside the script element, you define a function called animate() that animates the progress bar . It accepts the number of milliseconds the animation has to last. Inside it, you instantiate a new Deferred object and add a progress callback that updates the percentage of completion .

Using JavaScript’s setInterval() function, you set up the core of the animate() function where you calculate the next step of the animation, which will always be to add another $barWidth/100 to the bar width, and notify the Deferred . Finally, when the animation is completed you resolve the Deferred using the deferred .resolve() method .

This example has shown you how to use the deferred.progress() method, but you might struggle to understand why you don’t place the statement to update the percentage inside the function passed to setInterval() and get rid of the Deferred object altogether. You could indeed do this, but the previous listing has given us the chance to lead you gradually toward a crucial concept: when to use the Deferred object or the Promise object and why.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *