jQuery Deferred Object (detailed description)

On January 31, jQuery 1.5 was released, one of the key innovations of which was the Deferred Object tool. It is about him that I want to tell in more detail in this article.

This new functionality of the library is aimed at simplifying the work with deferred calls of handlers (callbacks). Deferred Object, similar to jQuery, is chainable, but has its own set of methods. Deferred Object is able to register many handlers in the queue, call the handlers registered in the queue and switch the state to “completed” or “error” for synchronous or asynchronous functions.

general description


The idea is quite simple: you need to create a Deferred Object, give this object to some external code that can hook handlers to it (or even not hook it), and then initiate the execution of these handlers by calling just one object method.

The whole process of using Deferred Object can be divided into three global stages:
  • A deferred object is created and initialized, the state of which is “not executed”.
  • The resulting object is issued to the external code, where handlers are added to the object in the queue by the methods deferred.then () , deferred.done () and deferred.fail () . Handlers can be of two types: “execution processing” and “error processing”.
  • To fulfill some condition (event) in the code where the object was created, the method for executing the handlers is called: deferred.resolve () calls the handlers for “successful completion” and translates the state of the object to “done”, deferred.reject () calls the handlers for “errors” and translates the state of the object to "canceled."

The word “queue” in the description of the Deferred Object is used for a reason - the handlers added to the queue are called in the order in which they were added (whoever added it earlier will be called earlier).

When adding handlers, it is not at all necessary to check the state of the deferred object (what if it is already executed or canceled). If the “run” / “error” handler is added to the already “run” / “canceled” object, it will be called immediately.

The repeated call of resolve / reject on the deferred object does not lead to a change in its state and the callback to the handlers, but is simply ignored (it doesn’t matter what resolve () or reject () was called before).

Deferred Object is “chained”, this means that its methods (not all) return the result of a reference to the source object, which makes it possible to call several methods of the object in a row, separating them with a dot. For example, like this: deferred.done (callback1) .fail (callback2);

Description of Methods


deferred.done (doneCallbacks)
adds a handler that will be called when the deferred object goes to the

deferred.fail (failCallbacks)
adds a handler that will be called when the deferred object goes to the

deferred.then (doneCallbacks, failCallbacks)
adds handlers of both types at once described above, equivalent to deferred.done (doneCallbacks) .fail (failCallbacks) entries

In the three methods described above, individual functions or function arrays can act as arguments to doneCallbacks, failCallback.

deferred.resolve (args)
sets the deferred object to the “executed” state and calls all doneCallbacks handlers with args parameters (the usual enumeration of parameters separated by commas)

deferred.reject (args)
puts the deferred object to the “canceled” state and calls all failCallbacks handlers with args parameters

Call context (this inside functions) of the handlers when using the two methods described above, the projection of the deferred object will be (or the object itself if the projection cannot be created) (for what the deferred projection is, see the description of deferred.promise () below). If you want to run handler functions with a different call context, then you need to use the following two methods:

deferred.resolveWith (context, args)
deferred.rejectWith (context, args)

the methods are completely similar to .resolve (args) and .reject (args), only context will be available inside the handler functions via the this keyword.

You can check the state of a deferred object using the methods:

deferred.isResolved ()
deferred.isRejected ()

methods return true or false depending on the current state of the object. At the time the handlers are executed (but the last has not yet been executed), the corresponding method will return true.

Creating a projection using deferred.promise ()


This method creates and returns a “projection” of the deferred object - this is a kind of copy of the object that has methods for adding handlers and checking the state. Those. we get a deferred object that works with the same queue of handlers as the original, allows you to add handlers to it, view the state of the original object, but does not allow you to change the state of the original object.

Creating a projection is necessary when you need to give the external code the ability to add handlers to the queue and at the same time protect yourself from unauthorized calls to methods for changing the state of an object.

Creating a Deferred Object


There are two ways to create a deferred object: create a new native instance using the $ .Deferred () method (the new keyword is optional) or call the $ .when (args) method to use deferred objects that were already created.

$ .Deferred (func)

Method returns a new deferred object. The func argument is optional, you can pass in a function that initializes the created object before returning it from the constructor. Inside the func function, you can access the initialized deferred object through this and / or through the first argument of the function.

$ .when (deferred1, deferred2, ...)

This method is used to create a deferred object that will go into the “executed” state after all its arguments (deferred objects) go into this state, or go into the “canceled” state as soon as at least one of its arguments is canceled.

If you want to wait for all tasks to be completed and only then complete our tasks, then in this case the place is to this method.
Example 1
$.when($.get('/echo/html/'), $.post('/echo/json/')).done(function(args1, args2) { alert("загрузка завершена"); });

This code will display the message “loading completed” only after both pages have been successfully loaded into the indicated blocks.

If a non-deferred object is encountered as the argument of $ .when (), then it is considered that this task has already been completed successfully and the method program is guided by the remaining arguments.

When the deferred object obtained by the $ .when () method goes into the “executed” state by the call context (this inside the function), the handlers will project this deferred object, and the arrays will be passed to the handlers as arguments, which will contain the corresponding execution arguments passed in $ .when () deferred objects (or one argument itself, if one). In the above example, args1 is the array of arguments to call success for the first ajax request, and args2 is for the second.

Using Deferred Object in $ .ajax ()


In jQuery 1.5, the ajax engine has been radically rewritten and now the $ .ajax () method and its shortened versions also use deferred objects. This makes it possible to assign several handlers to the request completion events (successful or by mistake) in a rather simple way, as well as to assign handlers to the completion of several parallel requests.

From the methods $ .ajax (), $ .get (), $ .post (), $ .getScript (), $ .getJSON (), an object is returned that has a projection of the deferred object, i.e. it has methods that allow you to add handlers to the queue and use this object as an argument to $ .when ().

In addition to this object, synonyms of methods are added, which by name are more suitable for the topic of ajax requests:
  • deferred.success = deferred.done - add a request completion handler
  • deferred.error = deferred.fail - add a request completion handler with an error
  • deferred.complete - add a request completion handler (whether it is successful or an error), is called after all the handlers added through success / done or error / fail

Interesting Facts


The $ .when () method, when evaluating its arguments for their belonging to deferred objects, checks for the presence of the .promise () method, and if there is such a method, then even the "left" object is considered deferred. A bug is associated with this, when accessing such “left” objects as if they were deferred leads to execution errors.

In the latest development versions of jQuery (jquery-git.js), the deferred object also has the .invert () method, which, similar to the .promise () method, creates a projection of the object only with the methods “vice versa”: done <=> fail, isResolved < => isRejected, promise <=> invert. The application of this method has not yet been noticed by me.

Examples of using


Example 1. (see above) already shows a solution to a typical problem of tracking the completion of several ajax requests.

Example 2
// запуск задач через 3 секунды
function test() {
  var d = $.Deferred();
  setTimeout(function() { d.resolve(); }, 3000);
  return d.promise();
}

var t = test().done(function() { alert("время истекло"); });

// пытаемся добавить задачу уже после выполнения
setTimeout(function() {
  alert("добавляем задачу поздно");
  t.done(function() { alert("выполнено"); });
}, 5000);

* This source code was highlighted with Source Code Highlighter.

This is a purely demo example, a deferred object is constructed, the state of which is translated into "completed" after 3 seconds, and is issued to the external code. In the external code, one handler is added immediately - it is called after 3 seconds, and the second after 5 seconds after construction, when the state is already "completed", so it is called immediately upon addition.

Example 3
1

2


* This source code was highlighted with Source Code Highlighter.

// нужно дождаться конца всей анимации
var a1 = $.Deferred(),
  a2 = $.Deferred();

$('#d1').slideUp(2000, a1.resolve);
$('#d2').fadeOut(4000, a2.resolve);

a1.done(function() { alert('a1'); });
a2.done(function() { alert('a2'); });
$.when(a1, a2).done(function() { alert('both'); });

* This source code was highlighted with Source Code Highlighter.

This code provides an example of solving a more real problem, when you need to execute some code after completing several animations. Adding an animation completion handler on one element is quite simple (this is also used), but tracking the completion of independent animations is somewhat more difficult. Using deferred objects and $ .when () makes code simple.

All examples can be run and checked on the jsfiddle.net service .

Materials:
blog.jquery.com/2011/01/31/jquery-15-released
api.jquery.com/category/deferred-object
www.erichynds.com/jquery/using -deferreds-in-jquery (translation: habrahabr.ru/blogs/jquery/112960 ) jQuery 1.5
source code and documentation

Also popular now: