Объект deferred

С помощью javascript можно устанавливать обработчики на некоторые события браузера (например, появление курсора над элементами страницы). Но что делать, если нужно организовать установку обработчиков выполнения другой задачи, например отдельно взятой функции? Для этого, в jQuery-1.5 был добавлен специальный вид объектов — jQuery.Deferred. Эти объекты хранят состояние выполнения задачи ("еще не выполнено", "выполнено", "ошибка при выполнении"), имеют методы для изменения этого состояния, а так же для установки обработчиков, реагирующих на переход объекта из состояния "еще не выполнено" в одно из двух других. (Более подробное описание находится после списка методов)

Объекты $.Deferred обладают следующими методами:

Содержание

Список методов

.done()
.fail()
.then()
.always()
регистрируют обработчики перехода объекта deferred в состояние "выполнено"/"ошибка выполнения" (rejected/rejected) (.then() регистрирует два обработчика сразу, а .always() общий обработчик на оба события).
.progress() регистрирует обработчики прогресса выполнения объекта deferred.
.resolved()
.reject()
переводят объект deferred из состояния "не выполнено" в "успешно выполнено"/"ошибка выполнения".
.notify() вызывает событие частичного выполнения deferred (его прогресса выполнения).
.resolveWith()
.rejectWith()
переводят объект deferred из состояния "не выполнено" в "успешно выполнено"/"ошибка выполнения", с указанием и контекста выполнения (значение переменной this) обработчиков.
.notifyWith() вызывает событие частичного выполнения deferred (его прогресса выполнения), с указанием и контекста выполнения (значение переменной this) обработчика.
.isResolved()
.isRejected()
проверяют, находится ли объект deferred в состоянии resolve/reject.
.state() возвращает состояние объекта deferred ('pending'/'resolve'/'reject').
.pipe() позволяет производить предварительную обработку параметров, которые в итоге будут переданы в обработчики.
.promise() возвращает заместителя объекта deferred, отличающегося от последнего отсутствием методов изменения состояния.
$(...).promise() (применяется к объекту jQuery!) создает заместителя, следящего за выполнением очереди событий (например анимаций) на выбранных элементах.
$.when() на основе нескольких заданных объектов, создает новый deferred-объект, следящий за состоянием всех заданных.

Создание

Для создания экземпляра объекта Deferred можно воспользоваться функцией

$.Deferred([function(obj)])

она возвращает новый deferred-объект. В качестве аргумента можно передать функцию, которая произведет инициализацию готового объекта, прямо перед его возвращением из конструктора. Внутри этой функции, deferred-объект будет доступен в переменной this и в первом аргументе (obj).

// создадим новенький, нетронутый объект Deferred.
var newDeferred = $.Deferred();
 
// создадим новый объект Deferred, установив на него обработчик
// успешного выполнения
var newDeferred2 = $.Deferred(function(obj){
  obj.done(someCallback);
});

Использование

При создании, объект jQuery.Deferred находится в состоянии unresolved (еще не выполнено). После этого, состояние объекта можно изменить на resolved (выполнено) с помощью метода .resolve() или .resolveWith(), а так же в состояние rejected (ошибка при выполнении), если вызвать метод .reject() или .rejectWith(). Важно отметить, что объект jQuery.Deferred может изменить свое состояние только один раз!

С помощью метода .done() можно установить обработчик удачного выполнения объекта Deferred, .fail() установит обработчик неудачного выполнения. В методе .then() можно задать оба вида обработчиков, а .always() установит обработчик, который будет вызван при переходе в любое из состояний. Если установить обработчик на объект Deferred, который уже находится в выполненном состоянии, то он (обработчик) будет запущен незамедлительно.

// Реализуем функцию test, которая запустит someAction() в течении 10 секунд.
// Возвращаемый объект Deferred будет оповещать о выполнении someAction()
function test(){
  var d = $.Deferred();
  var actTime = 10000*Math.random(); // время запуска 0-10 сек
  setTimeout(function(){
    someAction(); // выполняем интересующую функцию
    d.resolve();  // изменяем состояние Deferred-объекта на "выполнено"
  }, actTime);
  return d;
}
 
var defrr = test();
 
// устанавливаем обработчик выполнения Deferred-объекта
defrr.done(function(){ alert("someAction выполнен"); });
 
// установим еще один обработчик, однако на этот раз
// сделаем это на уже выполненном Deferred (спустя 15 сек)
setTimeout(function() {
  alert("добавляем задачу поздно");
  t.done(function(){ alert("someAction выполнен"); });
}, 15000);
 
// в итоге, первый обработчик будет вызван сразу после выполнения someAction, 
// а второй только через некоторое время (сразу после установки), 
// поскольку он будет установлен с опозданием.

Одной из важных особенностей объектов Deferred является то, что прикрепленные к ним обработчики всегда вызываются в том порядке, в котором они были установлены.


Начиная с jQuery-1.7, у объектов deferred появилась возможность обрабатывать промежуточные этапы (прогресс) выполнения. Это стало возможно благодаря введению методов .progress() и .notify(), с помощью которых можно устанавливать и вызывать обработчики прогресса выполнения объекта deferred. Установить обработчики прогресса можно и с помощь метода .then() задавая их в третьем параметре метода.

Особенности

Экземпляры jQuery.Deferred поддерживают цепочки методов, подобно объектам jQuery. Но не стоит забывать, что jQuery и Deferred обладают разными методами (т.е. на последнем нельзя, например, вызвать метод attr() или другие методы объекта jQuery).

var newDeferred = $.Deferred();
var exed = newDeferred.done(someDoneCallback) // установим обработчик выполнения
  .fail(someFailCallback) // установим обработчик выполнения с ошибкой
  .isResolved();  // проверим, выполнен ли объект (результат окажется в exed)


Среагировать на выполнение сразу нескольких deferred-объектов, можно с помощью функции $.when(deferred1, deferred2,...). Она создает новый объект deferred, который переходит в состояние удачного выполнения, когда все заданные объекты deferred примут это состояние. Если хотя бы один из них окажется в состоянии ошибки, то и исходный deferred перейдет в него же.

Заместитель (promise)

В большинстве случаев, состояние объекта deferred, не должно изменяться вне наблюдаемого с его помощью кода. Для соблюдения этого принципа, deferred объекты обладают методом .promise(), который возвращает заместителя исходного объекта. Заместители обладают тем же набором методов, что и сам объект, за исключением отсутствия методов изменяющих его состояние. Исправим, с учетом этого, пример из раздела "Использование":

function test(){
  var d = $.Deferred();
  var actTime = 10000*Math.random();
  setTimeout(function(){
    someAction();
    d.resolve();
  }, actTime);
  return d.promise(); // вместо самого объекта, вернем его заместителя
}
 
var defrr = test();
// в отличие от предыдущего примера, здесь defrr не обладает методами 
// изменения состояния (такими как resolved() и др.)
 
// все остальные возможности Deferred-объекта остаются неизменными.
// можно по прежнему добавлять обработчики выполнения:
defrr.done(function(){ alert("someAction выполнен"); });
 
// или узнавать состояние объекта
if(defrr.isResolved())
  alert("Проверка подтвердила удачное выполнение функции someAction.");
Поисковые ключи:
  • deferred