What is a Deferred object?

When developing websites, we often encounter some javascript operations that take a long time. There are both asynchronous operations (such as Ajax readings of server data) and synchronous operations (such as traversing a large array) that do not yield immediate results.

Typically, they are assigned callback functions. That is, specify in advance which functions should be called once they have finished running.

However, jQuery is very weak when it comes to callback functions. To change this, the jQuery development team designed deferred objects.

In short, the Deferred object is jQuery’s callback solution. ** In English, defer means “defer”, so the deferred object means “defer” to some future point.

It solves the problem of how to handle time-consuming operations, provides better control over those operations, and a unified programming interface. Its main functions can be summed up in four points. Let’s go through the example code step by step.

Second, the chain writing of Ajax operations

First, let’s review the traditional way jQuery writes Ajax operations:

$.ajax({url: "test.html", success: function(){alert(" ha ha! ); }, error:function(){alert(" error! ); }});Copy the code

In the above code, $.ajax() takes an object argument that contains two methods: the SUCCESS method specifies the callback after the operation succeeds, and the error method specifies the callback after the operation fails.

$.ajax() returns an XHR object, and you can’t chain it if you’re using jQuery earlier than 1.5.0. After version 1.5.0, the deferred object is returned and can be chained.

Now, the new way to write it is this:

$.ajax("test.html").done(function(){alert(" ha ha! ); }).fail(function(){alert(" error! ); });Copy the code

As you can see, done() equals the success method and fail() equals the error method. The readability of the code is greatly improved by the adoption of chain writing.

Specify multiple callback functions for the same operation

The nice thing about deferred objects is that they allow you to add multiple callbacks freely. For example, if the Ajax operation succeeds and I want to run another callback function in addition to the original one, what happens? It’s easy. Just add it to the end.

$.ajax("test.html").done(function(){alert(" ha ha! ); }).fail(function(){alert(" error! ); }). Done (function(){alert(" second callback!" ); });Copy the code

Callbacks can be added as many as they want, and they are executed in the order they were added.

Specify a callback function for multiple operations

Another benefit of the Deferred object is that it allows you to specify a callback function for multiple events, which is not possible in traditional writing.

Take a look at the following code, which uses a new method $.when() :

$. When ($. Ajax (" test1. HTML "), $. Ajax (" test2. HTML ")). The done (function () {alert (" ha! Ha! Success!" ); }).fail(function(){alert(" error! ); });Copy the code

Ajax (“test1.html”) and.ajax(“test1.html”) and.ajax(“test1.html”) and.ajax(“test1.html”), and if they both succeed, Run the callback specified by done(); If one or both fail, the callback function specified by fail() is executed.

5. Callback function interface for common operations (1)

The great advantage of the Deferred object is that it extends the callback function interface from Ajax operations to all operations. That is, any operation —-, whether ajax or local, asynchronous or synchronous, —-, can specify a callback using various methods of the Deferred object. Let’s look at a specific example. Suppose there is a time-consuming operation wait:

Var tasks = function(){var tasks = function(){alert(" ); }; SetTimeout (tasks, 5000); };Copy the code

How do we specify a callback function for it?

It’s natural to think that you can use $.when() :

$.when(wait()).done(function(){alert(" ha ha! ); }).fail(function(){alert(" error! ); });Copy the code

However, written this way, the done() method executes immediately and does not act as a callback. The reason is that $.when() can only be deferred, so wait() must be overwritten:

var dtd = $.Deferred(); Var wait = function(DTD){var tasks = function(){alert(" Execution completed! ") ); dtd.resolve(); // Change the execution state of the Deferred object}; SetTimeout (tasks, 5000); Return the DTD; };Copy the code

Now that wait() returns the Deferred object, you can add the chain operation.

$.when(wait(DTD)).done(function(){alert(" ha ha! ); }).fail(function(){alert(" error! ); });Copy the code

When wait() is finished, the callback specified by the done() method is automatically run.

Defer. Resolve () and defer. Reject ()

If you look closely, you’ll notice that there is one other area in the wait() function above that I haven’t covered. What does DTd.resolve () do?

To address this problem, we need to introduce a new concept called “execution state.” JQuery states that a Deferred object can be executed in three states —- Unfinished, completed, and failed. If the execution state is “resolved,” the deferred object immediately calls the callback specified by the done() method. If the execution status is “failed”, the callback function specified by the fail() method is called; If the execution status is “incomplete,” continue to wait or call the callback function specified by the progress() method (added in Version 1.7).

In the previous ajax operations, the Deferred object automatically changes its execution state based on the result returned. However, in wait(), this execution state must be manually specified by the programmer. Dtd.resolve () means that the execution state of the DTD object is changed from “incomplete” to “completed,” triggering the done() method.

Similarly, there is a deferred.reject() method that changes the execution state of a DTD object from “incomplete” to “failed,” triggering the fail() method.

var dtd = $.Deferred(); Var wait = function(DTD){var tasks = function(){alert(" Execution completed! ") ); dtd.reject(); // Change the execution state of the Deferred object}; SetTimeout (tasks, 5000); Return the DTD; }; $.when(wait(DTD)).done(function(){alert(" ha ha! ); }).fail(function(){alert(" error! ); });Copy the code

Deferred. Promise (

There’s something wrong with writing it this way. That is, a DTD is a global object, so its execution state can be changed externally.

Take a look at the following code:

var dtd = $.Deferred(); Var wait = function(DTD){var tasks = function(){alert(" Execution completed! ") ); dtd.resolve(); // Change the execution state of the Deferred object}; SetTimeout (tasks, 5000); Return the DTD; }; $.when(wait(DTD)).done(function(){alert(" ha ha! ); }).fail(function(){alert(" error! ); }); dtd.resolve();Copy the code

I added a line at the end of the code to DTD.resolve (), which changed the execution state of the DTD object, thus causing the done() method to execute immediately, jumping out of the “ha-ha! “And wait 5 seconds before popping out the message” Completed!” “Is displayed.

To avoid this, jQuery provides the deferred.promise() method. It returns another deferred object on top of the original one, which only opens methods that are not related to changing the execution state (such as done() and fail()), and blocks methods that are related to changing the execution state (such as resolve() and Reject ()). So that the execution state cannot be changed.

Take a look at the following code:

var dtd = $.Deferred(); Var wait = function(DTD){var tasks = function(){alert(" Execution completed! ") ); dtd.resolve(); // Change the execution state of the Deferred object}; SetTimeout (tasks, 5000); return dtd.promise(); // Return the promise object}; var d = wait(dtd); $.when(d).done(function(){alert(" ha, ha, success! "); ); }).fail(function(){alert(" error! ); }); d.resolve(); // At this point, the statement is invalidCopy the code

In the code above, wait() returns a Promise object. We then bind the callback function to this object instead of the original Deferred object. The advantage of this is that there is no way to change the execution state of the object. You can only change the execution state of the original Deferred object.

A better way to write it, however, is to make the DTD object internal to the wait() function, as Allenm points out.

Var wait = function(DTD){var DTD = $.deferred (); Var tasks = function(){alert(" Done! ); dtd.resolve(); // Change the execution state of the Deferred object}; SetTimeout (tasks, 5000); return dtd.promise(); // Return the promise object}; $.when(wait()).done(function(){alert(" ha ha! ); }).fail(function(){alert(" error! ); });Copy the code

8. Callback function interface for common operations (middle)

Another way to prevent the execution state from being changed externally is to use the deferred object’s constructor $.deferred ().

At this point, the wait function remains unchanged and we pass it directly to $.deferred () :

$. Deferred (wait). Done (function () {alert (" ha! Ha! Success!" ); }).fail(function(){alert(" error! ); });Copy the code

.deferred () can take a function name as an argument,.deferred () can take a function name as an argument,.deferred () can take a function name as an argument,.deferred () can take a function name as an argument,.deferred () can take a function name as an argument, The Deferred object generated by.deferred () will be the default argument to this function.

9. Callback function interface for common operations (part 2)

In addition to the above two approaches, we can also deploy the Deferred interface directly on the Wait object.

var dtd = $.Deferred(); Var wait = function(DTD){var tasks = function(){alert(" Done!" ); dtd.resolve(); // Change the execution state of the Deferred object}; SetTimeout (tasks, 5000); }; DTD. Promise (wait); Waiter. done(function(){alert(" ha ha! ); }).fail(function(){alert(" error! ); }); wait(dtd);Copy the code

The key here is the DTD.Promise (wait) line, which deploys the Deferred interface on the Wait object. Because of this line, done() and fail() can be called directly on wait later.

Summary: Methods of deferred objects

Now that we’ve covered the various methods for deferred objects, here’s a summary:

(1) $.deferred () generates a Deferred object.

(2) deferred. Done () specifies the callback function if the operation succeeds

(3) deferred.fail() specifies the callback function if the operation fails

(4) Deferred. Promise () Returns a new Deferred object with no arguments. The running state of the object cannot be changed. When a parameter is accepted, the effect is to deploy the Deferred interface on the parameter object.

(5) Deferred.resolve () manually changes the deferred object’s running state to “Done”, triggering the done() method immediately.

This method is the opposite of deferred.reject(). It changes the deferred object’s running state to “failed”, triggering the fail() method immediately.

(7) $.when() specifies the callback function for multiple operations.

In addition to these methods, the Deferred object has two important methods that are not covered in the tutorial above.

(8) deferred. Then ()

Sometimes it’s easy to write done() and fail() together. This is called the then() method.

$.when($.ajax("/main.php")). Then (successFunc, failureFunc);Copy the code

If then() has two arguments, the first argument is the callback to the done() method and the second argument is the callback to the fail() method. If then() has only one argument, it is equivalent to done().

(9) deferred. Always ()

This method is also used to specify that the callback is always executed whether deferred. Resolve () or deferred. Reject () is called.

$.ajax("test.html"). Always (function() {alert(" executed! ); });Copy the code

(acknowledgments: after the first draft of this article was published, allenm wrote to point out that the original had gotten promise() wrong. The current second draft is based on his article, and I would like to extend my heartfelt thanks.

(after)

Turn to www.ruanyifeng.com/blog/2011/0…