Home >Web Front-end >JS Tutorial >An Introduction to jQuery's Deferred Objects

An Introduction to jQuery's Deferred Objects

Christopher Nolan
Christopher NolanOriginal
2025-02-18 11:08:10416browse

An Introduction to jQuery's Deferred Objects

JavaScript developers have long used callback functions to perform multiple tasks. A very common example is adding a callback through the addEventListener() function to perform various actions when events such as clicks or keys are triggered. The callback function is simple and easy to use and is suitable for simple scenarios. However, when web page complexity increases and many asynchronous operations need to be performed in parallel or sequentially, the callback function becomes difficult to manage.

ECMAScript 2015 (also known as ECMAScript 6) introduces a native method for handling such situations: Promise. If you don't know about Promise, you can read the article "Overview of JavaScript Promise". jQuery provides and still provides its own version of Promise, called the Deferred object. A few years before Promise was introduced into ECMAScript, the Deferred object was already introduced into jQuery. This article will discuss what Deferred objects are and what problems they are trying to solve.

Key Points

  • Deferred Objects Simplify Asynchronous Programming: jQuery's Deferred object provides a powerful way to manage and coordinate asynchronous operations, reducing the complexity associated with traditional callback functions.
  • Interoperability with ECMAScript Promise: Although the implementation is different, the 3.x version of jQuery Deferred object improves compatibility with native ECMAScript 2015 Promise, enhancing its modern web Practicality in development.
  • Flexible method: Deferred object provides a variety of methods, such as resolve(), reject(), done(), fail(), then(),
  • and
  • , allowing developers to fine-grained control of the processing of asynchronous processes. .
  • Avoid callback hell:
  • By using Deferred and Promise objects, developers can avoid the common pitfalls of deeply nested callbacks, thus writing more readable and maintainable code. catch()Enhanced Error Handling:
  • jQuery 3 introduces the
method of the Deferred object, which is consistent with the ECMAScript standard and provides a simplified method to handle errors in the Promise chain.

Brief History

Deferred object was introduced in jQuery 1.5, which is a chain-callable utility for registering multiple callbacks into a callback queue, calling the callback queue, and passing the success or failure status of any synchronous or asynchronous functions . Since then, it has been a subject of discussion, some criticism and many changes. Some examples of criticism include "You Missed the Promise's Point" and "JavaScript Promise and Why jQuery Is Is the Implementation of Wrong".

Together with the Promise object, Deferred represents jQuery's implementation of Promise. In jQuery versions 1.x and 2.x, the Deferred object follows the CommonJS Promises/A proposal. This proposal is used as the basis for the Promises/A proposal, and native Promise was built on this proposal. As stated in the introduction, the reason jQuery does not follow the Promises/A proposal is that it implemented Promise long before the proposal was proposed.

Because jQuery is a pioneer, and there is a difference in how to use Promise in pure JavaScript and jQuery 1.x and 2.x due to backward compatibility issues. Furthermore, since jQuery follows different proposals, the library is incompatible with other libraries that implement Promise, such as Q library.

Interoperability with native Promise, such as implemented in ECMAScript 2015, has been improved in the upcoming jQuery 3. The signatures of the main method (then()) are still slightly different for backward compatibility reasons, but the behavior is more standard-compliant.

Callback function in jQuery

To understand why you might need to use a Deferred object, let's discuss an example. When using jQuery, it often uses its Ajax method to perform asynchronous requests. For example, suppose you are developing a webpage that sends Ajax requests to the GitHub API. Your goal is to retrieve the user's repository list, find the recently updated repository, find the first file with the name "README.md", and finally retrieve the contents of the file. According to this description, each Ajax request can only start after the previous step is completed. In other words, the requests must be run in order.

Convert this description to pseudo-code (note that I'm not using the real GitHub API), we get:

<code class="language-javascript">var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;

  $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files', function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content', function(content) {
      console.log('The content of the file is: ' + content);
    });
  });
});</code>
As you can see in this example, using the callback function, we have to nest the calls to execute the Ajax requests in the order we want. This makes the code less readable. There are many nested callbacks, or independent callbacks that must be synchronized, often referred to as "callback hell".

For a little improvement, you can extract the named function from the anonymous inline function I created. However, this change didn't help much and we still find ourselves in callback hell. Deferred and Promise objects are needed at this time.

Deferred and Promise objects

Deferred objects can be used when performing asynchronous operations such as Ajax requests and animations. In jQuery, the Promise object is created from a Deferred object or a jQuery object. It has a subset of the Deferred object methods:

, always(), done(), fail(), state(), and then(). I will cover these and other methods in the next section.

If you come from the native JavaScript world, you may be confused by the existence of these two objects. Why do there are two objects (Deferred and Promise) when JavaScript has only one (Promise)? To explain the differences and their use cases, I will take the same metaphor I used in my book jQuery in Action, Third Edition.

If you write a function that handles asynchronous operations and should return values ​​(can also be an error or no value at all), you usually use a Deferred object. In this case, your function is the producer of the value and you want to prevent the user from changing the state of Deferred. Use the Promise object when you are a consumer of a function.

To clarify this concept, suppose you want to implement a Promise-based timeout() function (I will show you the code for this example in a subsequent section of this article). You are responsible for writing a function that must wait for a given amount of time (in which case the value is not returned). This makes you a producer. The consumer of your function does not care about parsing or rejecting it. The user only needs to be able to add functions to execute when Deferred is completed, failed, or progressed. Additionally, you want to make sure that the user cannot parse or reject Deferred on its own. To achieve this, you need to return the Deferred Promise object created in the timeout() function, instead of the Deferred itself. By doing this, you can make sure that no one can call the timeout() or resolve() methods except the reject() function.

You can read more about the difference between the Deferred and Promise objects of jQuery in this StackOverflow question.

Now that you know what these objects are, let's take a look at the available methods.

Deferred method

Deferred objects are very flexible and provide a way to meet all your needs. It can be created by calling the jQuery.Deferred() method as follows:

<code class="language-javascript">var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;

  $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files', function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content', function(content) {
      console.log('The content of the file is: ' + content);
    });
  });
});</code>

Or, use $ shortcut:

<code class="language-javascript">var deferred = jQuery.Deferred();</code>

After creating, the Deferred object exposes multiple methods. Ignore those deprecated or deleted methods, they are:

  • always(callbacks[, callbacks, ..., callbacks]): Add the handler to be called when the Deferred object is parsed or rejected.
  • done(callbacks[, callbacks, ..., callbacks]): Add the handler to be called when the Deferred object is parsed.
  • fail(callbacks[, callbacks, ..., callbacks]): Add the handler to be called when the Deferred object is rejected.
  • notify([argument, ..., argument]): Call the progressCallbacks of the Deferred object with the given parameter.
  • notifyWith(context[, argument, ..., argument]): Call the progressCallbacks of the Deferred object with the given context and parameters.
  • progress(callbacks[, callbacks, ..., callbacks]): Add the handler to be called when the Deferred object generates progress notification.
  • promise([target]): Returns the Deferred Promise object.
  • reject([argument, ..., argument]): Defends the Deferred object and calls any failCallbacks with the given parameter.
  • rejectWith(context[, argument, ..., argument]): Defends the Deferred object and calls any failCallbacks with the given context and parameters.
  • resolve([argument, ..., argument]): parse the Deferred object and call any doneCallbacks with the given parameter.
  • resolveWith(context[, argument, ..., argument]): parses the Deferred object and calls any doneCallbacks with the given context and parameters.
  • state(): Determine the current state of the Deferred object.
  • then(resolvedCallback[, rejectedCallback[, progressCallback]]): Add the handler to be called when the Deferred object is parsed, rejected, or is still in progress.

The descriptions of these methods give me the opportunity to emphasize the difference between jQuery documentation and the terms used by the ECMAScript specification. In the ECMAScript specification, when the Promise has been completed or rejected, it is said that the Promise has been resolved. However, in jQuery's documentation, the word "resolved" is used to refer to a state called "fulfilled" in the ECMAScript specification.

Due to the numerous methods provided, all methods cannot be covered in this article. However, in the next section, I will show you a few examples of Deferred and Promise using. In the first example, we will override the code snippets checked in the "Callback Functions in jQuery" section, but we will use these objects instead of the callback functions. In the second example, I will clarify the producer-consumer analogy discussed.

Execute Ajax requests using Deferred order

In this section, I will show how to use the Deferred object and some of its methods to improve the readability of the code developed in the "Callback Functions in jQuery" section. Before we dig deeper, we must understand what methods we need.

Based on our needs and the list of methods provided, it is obvious that we can use the done() or then() methods to manage successful situations. Since many of you may have become accustomed to JavaScript's Promise object, in this example I'll use the then() method. An important difference between these two methods is that then() is able to forward the received parameter value to other then(), done(), fail() or progress() calls defined after it.

The final result is as follows:

<code class="language-javascript">var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;

  $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files', function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content', function(content) {
      console.log('The content of the file is: ' + content);
    });
  });
});</code>

As you can see, the code is easier to read, because we are able to break the whole process into several small steps at the same level (about indentation).

Create a Promise-based setTimeout function

As you know, setTimeout() is a function that executes a callback function after a given amount of time. Both elements (callback function and time) should be provided as parameters. Suppose you want to log the message to the console in one second. By using the setTimeout() function, you can achieve this with the code shown below:

<code class="language-javascript">var deferred = jQuery.Deferred();</code>

As you can see, the first parameter is the function to be executed, and the second parameter is the number of milliseconds to wait. This function has been working well for years, but what if you need to introduce latency into the Deferred chain?

In the following code, I will show you how to use the Promise object provided by jQuery to develop a Promise-based setTimeout() function. To do this, I will use the promise() method of the Deferred object.

The final result is as follows:

<code class="language-javascript">var deferred = $.Deferred();</code>

In this list, I define a function called timeout() which wraps the native setTimeout() function of JavaScript. Inside timeout(), I create a new Deferred object to manage an asynchronous task that involves parsing the Deferred object after the specified number of milliseconds. In this case, the timeout() function is the producer of the value, so it creates the Deferred object and returns the Promise object. By doing this, I make sure that the caller (consumer) of the function cannot parse or reject the Deferred object at will. In fact, the caller can only use methods such as done() and fail() to add the functions to be executed.

Difference between jQuery 1.x/2.x and jQuery 3

In the first example using Deferred, we developed a snippet of code that looks for a file with the name "README.md", but we did not consider the case where such a file could not be found. This situation can be seen as a failure. When this happens, we may want to break the call chain and jump directly to the end of it. To do this, an exception is naturally thrown and catch it using the fail() method, just like the catch() method of JavaScript.

In libraries that comply with Promises/A and Promises/A (for example, jQuery 3.x), thrown exceptions are converted to rejects and failed callbacks are called (for example, added with fail()). This receives the exception as a parameter.

In jQuery 1.x and 2.x, uncaught exceptions will stop the execution of the program. These versions allow thrown exceptions to bubbling, usually reaching window.onerror. If no function is defined to handle this exception, an exception message is displayed and the execution of the program is aborted.

To better understand different behaviors, check out this example:

<code class="language-javascript">var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;

  $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files', function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content', function(content) {
      console.log('The content of the file is: ' + content);
    });
  });
});</code>

In jQuery 3.x, this code writes the messages "First failure function" and "Second success function" to the console. The reason is that, as I mentioned earlier, the specification states that the thrown exception should be converted to a rejection and that the failed callback must be called using the exception. Furthermore, once the exception is processed (handled by a failed callback passed to the second then() in our example), the following successful function should be executed (in this case the successful callback passed to the third then() in this case) ).

In jQuery 1.x and 2.x, no other function is executed except the first function (the function that throws the error), you will only see the message "Uncaught Error: An error message”.

To further improve its compatibility with ECMAScript 2015, jQuery 3 also added a new method in Deferred and Promise objects catch(). This is a way to execute a handler when a Deferred object is rejected or its Promise object is in a rejected state. Its signature is as follows:

<code class="language-javascript">var deferred = jQuery.Deferred();</code>

This method is just a shortcut to then(null, rejectedCallback).

Conclusion

In this article, I introduced you to the implementation of jQuery's Promise. Promise allows you to avoid the need to use nasty tricks to synchronize parallel asynchronous functions and nested callbacks...

In addition to showing some examples, I also covered how jQuery 3 improves interoperability with native Promise. Despite highlighting the differences between older jQuery and ECMAScript 2015, Deferred is still a very powerful tool in your toolbox. As a professional developer, you will find yourself using it frequently as the project becomes more difficult.

FAQ for jQuery Deferred Objects (FAQ)

(The FAQ part is omitted here because the article is too long and has little to do with the main theme of the article. If necessary, you can ask the FAQ question separately.)

The above is the detailed content of An Introduction to jQuery's Deferred Objects. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn