Detailed explanation of jQuery’s deferred object
The development speed of jQuery is very fast, with a major version almost every six months and a small version every two months.
Each version will introduce some new features. What I want to introduce today is the deferred object , a new feature introduced from jQuery 1.5.0 .
This feature is very important and will become the core method of jQuery in the future. It has completely changed how to use ajax in jQuery. In order to achieve it, all the ajax code of jQuery has been rewritten. However, it is relatively abstract and difficult for beginners to master, and there are not many online tutorials. So, I have sorted out my study notes, I hope it will be useful to everyone.
This article is not a preliminary tutorial. The readers are developers who already have experience using jQuery. If you want to understand the basic usage of jQuery, please read “jQuery Design Ideas” and “jQuery Best Practices” written by me .
=====================================
Detailed explanation of jQuery’s deferred object
Author: Ruan Yifeng
1. What is a deferred object?
In the process of developing a website, we often encounter some time-consuming javascript operations. Among them, there are both asynchronous operations (such as ajax reading server data) and synchronous operations (such as traversing a large array), and none of them can get results immediately.
The usual practice is to specify a callback function (callback) for them. That is, it is specified in advance which functions should be called once they have finished running.
However, in terms of callback functions, jQuery is very weak. To change this, the jQuery development team designed the deferred object .
Simply put, the deferred object is jQuery’s callback function solution. In English, defer means “delay”, so the meaning of deferred object is “delay” until a certain point in the future.
It solves the problem of how to deal with time-consuming operations, provides better control over those operations, and a unified programming interface. Its main functions can be summarized into four points. Let’s learn step by step through sample code.
Second, the chain writing of ajax operation
First, review the traditional way of writing jQuery’s ajax operation:
$.ajax({
url: “test.html”,
success: function(){
alert(“Haha, success!”);
},error:function(){
alert(“Error!”);
}});
(Run code example 1 )
In the above code, $.ajax() accepts an object parameter. This object contains two methods: the success method specifies the callback function after the operation is successful, and the error method specifies the callback function after the operation fails.
After the $.ajax() operation is completed, if you are using a version of jQuery lower than 1.5.0, the XHR object is returned, and you cannot perform chain operations; if the version is higher than 1.5.0, the deferred object is returned, Chain operation is possible.
Now, the new wording is like this:
$.ajax(“test.html”)
.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
(Run code example 2 )
As you can see, done() is equivalent to the success method, and fail() is equivalent to the error method. After adopting the chain writing method, the readability of the code is greatly improved.
Three, specify multiple callback functions for the same operation
One of the great benefits of the deferred object is that it allows you to add multiple callback functions freely.
Or take the above code as an example. What should I do if I want to run another callback function in addition to the original callback function after the ajax operation is successful?
It’s very simple, just add it to the back.
$.ajax(“test.html”)
.done(function(){ alert(“Haha, success!”);})
.fail(function(){ alert(“Error!”);})
.done(function(){ alert(“The second callback function!”);} );
(Run code example 3 )
Any number of callback functions can be added, and they are executed in the order in which they are added.
Four, specify callback functions for multiple operations
Another great advantage of the deferred object is that it allows you to specify a callback function for multiple events, which is not possible with traditional writing.
Please take a look at the following code, it uses a new method $.when() :
$.when($.ajax(“test1.html”), $.ajax(“test2.html”))
.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
(Run code example 4 )
The meaning of this code is to perform two operations $.ajax(“test1.html”) and $.ajax(“test2.html”) first, if both are successful, run the callback function specified by done(); if If one fails or both fail, the callback function specified by fail() is executed.
5. Callback function interface for common operations (on)
The biggest advantage of the deferred object is that it extends this set of callback function interfaces from ajax operations to all operations. In other words, any operation-whether it is an ajax operation or a local operation, and whether it is an asynchronous operation or a synchronous operation-can use various methods of the deferred object to specify a callback function.
Let’s look at a concrete example. Suppose there is a very time-consuming operation wait:
var wait = function(){
var tasks = function(){
alert(“Execution finished!”);
};
setTimeout(tasks,5000);
};
We specify a callback function for it, what should we do?
Naturally, you will think that you can use $.when():
$.when(wait())
.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
(Run code example 5 )
However, if written in this way, the done() method will be executed immediately and will not function as a callback function. The reason is that the parameter of $.when() can only be a deferred object, so wait() must be rewritten:
var dtd = $.Deferred(); // Create a new deferred object
var wait = function(dtd){
var tasks = function(){
alert(“Execution finished!”);
dtd.resolve(); // Change the execution state of the deferred object
};
setTimeout(tasks,5000);
return dtd;
};
Now, the wait() function returns a deferred object, which can be chained.
$.when(wait(dtd))
.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
(Run code sample 6 )
After the wait() function runs, the callback function specified by the done() method will be automatically run.
Six, deferred.resolve() method and deferred.reject() method
If you look closely, you will find that in the wait() function above, there is one more thing I didn’t explain. That is what is the role of dtd.resolve() ?
To clarify this issue, we must introduce a new concept “execution state”. jQuery stipulates that the deferred object has three execution states-unfinished, completed and failed. If the execution status is “resolved” (resolved), the deferred object immediately calls the callback function 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 “unsuccessful” “Done”, continue to wait, or call the callback function specified by the progress() method (added in jQuery 1.7).
During the ajax operation in the previous part, the deferred object will automatically change its own execution state according to the returned result; however, in the wait() function, this execution state must be manually specified by the programmer. The meaning of dtd.resolve() is to change the execution status of the dtd object from “incomplete” to “completed”, thereby triggering the done() method.
Similarly, there is also a deferred.reject() method, which changes the execution status of the dtd object from “incomplete” to “failed”, thereby triggering the fail() method.
var dtd = $.Deferred(); // Create a new Deferred object
var wait = function(dtd){
var tasks = function(){
alert(“Execution finished!”);
dtd.reject(); // Change the execution state of the Deferred object
};
setTimeout(tasks,5000);
return dtd;
};
$.when(wait(dtd))
.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
(Run code example 7 )
Seven, deferred.promise() method
There are still problems with the above writing. That is, dtd is a global object, so its execution state can be changed from the outside.
Please look at the following code:
var dtd = $.Deferred(); // Create a new Deferred object
var wait = function(dtd){
var tasks = function(){
alert(“Execution finished!”);
dtd.resolve(); // Change the execution state of the Deferred object
};
setTimeout(tasks,5000);
return dtd;
};
$.when(wait(dtd))
.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
dtd.resolve();
(Run code sample 8 )
I added a line of dtd.resolve() at the end of the code, which changed the execution state of the dtd object, and caused the done() method to be executed immediately. The prompt box of “Haha, succeeded!” popped out, and waited 5 seconds later. The prompt box of “Execution Completed!” popped out.
To avoid this situation, jQuery provides the deferred.promise() method. Its function is to return another deferred object on the original deferred object, which only opens methods that are not related to changing the execution state (such as done() method and fail() method), and shields methods related to changing the execution state ( Such as resolve () method and reject () method), so that the execution state cannot be changed.
Please look at the following code:
var dtd = $.Deferred(); // Create a new Deferred object
var wait = function(dtd){
var tasks = function(){
alert(“Execution finished!”);
dtd.resolve(); // Change the execution state of the Deferred object
};
setTimeout(tasks,5000);return dtd.promise(); // return promise object
};
var d = wait(dtd); // Create a new d object and operate on this object instead
$.when(d)
.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
d.resolve(); // At this time, this statement is invalid
(Run code sample 9 )
In the above code, the wait() function returns a promise object. Then, we bind the callback function to this object instead of the original deferred object. The advantage of this is that the execution state of this object cannot be changed. If you want to change the execution state, you can only manipulate the original deferred object.
However, a better way of writing is pointed out by allenm , turning the dtd object into an internal object of the wait() function.
var wait = function(dtd){
var dtd = $.Deferred(); //In the function, create a new Deferred object
var tasks = function(){
alert(“Execution finished!”);
dtd.resolve(); // Change the execution state of the Deferred object
};
setTimeout(tasks,5000);return dtd.promise(); // return promise object
};
$.when(wait())
.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
(Run code sample 10 )
8. Callback function interface for common operations (middle)
Another way to prevent the execution state from being changed externally is to use the constructor $.Deferred() of the deferred object.
At this time, the wait function remains unchanged, and we directly pass it to $.Deferred():
$.Deferred(wait)
.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
(Run code sample 11 )
jQuery stipulates that $.Deferred() can accept a function name (note the function name) as a parameter, and the deferred object generated by $.Deferred() will be the default parameter of this function.
Nine, the callback function interface for common operations (below)
In addition to the above two methods, we can also directly deploy the deferred interface on the wait object.
var dtd = $.Deferred(); // Generate Deferred object
var wait = function(dtd){
var tasks = function(){
alert(“Execution finished!”);
dtd.resolve(); // Change the execution state of the Deferred object
};
setTimeout(tasks,5000);
};
dtd.promise(wait);
wait.done(function(){ alert(“Haha, success!”); })
.fail(function(){ alert(“Error!”); });
wait(dtd);
(Run code sample 12 )
The key here is the line dtd.promise(wait), its role is to deploy the Deferred interface on the wait object. It is precisely because of this line that done() and fail() can be called directly on wait later.
10. Summary: Methods of deferred objects
The various methods of deferred objects have been mentioned before, here is a summary:
(1) $.Deferred() generates a deferred object.
(2) deferred.done() specifies the callback function when the operation is successful
(3) deferred.fail() specifies the callback function when the operation fails
(4) When deferred.promise() has no parameters, it returns a new deferred object, and the running state of the object cannot be changed; when accepting parameters, it serves to deploy the deferred interface on the parameter object.
(5) deferred.resolve() manually changes the running state of the deferred object to “completed”, thereby immediately triggering the done() method.
(6) The deferred.reject() method is just the opposite of deferred.resolve(). After the call, the running state of the deferred object is changed to “failed”, thereby immediately triggering the fail() method.
(7) $.when() specifies the callback function for multiple operations.
In addition to these methods, the deferred object has two important methods, which are not covered in the above tutorial.
(8) deferred.then()
Sometimes in order to save trouble, you can write done() and fail() together. This is the then() method.
$.when($.ajax( “/main.php” ))
.then(successFunc, failureFunc );
If then() has two parameters, then the first parameter is the callback function of the done() method, and the second parameter is the callback method of the fail() method. If then() has only one parameter, it is equivalent to done().
This method is also used to specify the callback function. Its function is that no matter whether the call is deferred.resolve() or deferred.reject(), it will always be executed at the end.
$.ajax( “test.html”)
.always( function() {alert(“Executed!”);} );
(Acknowledgements: After the first draft of this article was published, allenm wrote to point out that the original understanding of promise() was wrong. The second draft is now revised based on his article , and I would like to express my sincere thanks.)
(over)