Deferred

Объект $.Deferred

Начнем с объекта Deferred, разобраться с ним и уметь применять – это прям высший пилотаж. Давайте посмотрим, как он работает, откройте нашу песочницу:

Запустите скрипт в консоли:

// инициализация Deferred объекта
// статус «ожидает выполнение»
var D = $.Deferred();

// подключаем обработчики
D.then(function() { console.log("first") });
D.then(function() { console.log("second") });

// изменяем статус на «fulfilled» - «выполнен успешно»
// для этого вызываем resolve()
// для наглядности подождём секундочку
// наши обработчики будут вызваны в порядке очереди, но они не ждут друг друга
setTimeout(() => D.resolve(), 1000)

// данный обработчик подключён слишком поздно и будет вызван сразу
D.then(function() { console.log("third") });

Приведенные примеры будут работать на любой странице, где подключен jQuery ;)

Если всё это перевести на человеческий язык, получится следующий сценарий:

  • если всё будет хорошо, тогда выполни вот эту функцию и выведи «first»

  • и ещё вот эту функцию — «second»

  • resolve() — мы узнали, всё хорошо

  • если всё хорошо, выполняем функцию и выводим «third»

Кроме сценариев с «happy end», есть ещё и грустные истории, когда всё пошло не так, как нам бы хотелось:

Получается так:

  • если всё будет хорошо, тогда выполни вот эту функцию — «done»

  • если всё будет плохо, тогда вот эта функция выведет «fail»

  • ой, всё плохо

В действительности, метод then() позволяет вешать одновременно как обработчики для положительного сценария, так и для варианта с ошибкой:

Становится ли от этого код читаемым – сомневаюсь, но такой вариант существует и используется повсеместно. Я же предпочитаю для отлова ошибок использовать метод catch(), который по своей сути лишь сокращенная запись для then(null, fn):

Ещё упомяну метод always() – он добавляет обработчики, которые будут выполнены вне зависимости от случившегося (в действительности, внутри происходит вызов done(arguments) и fail(arguments)).

Чтобы не путаться в перечисленных методах, приведу блок-схему:

При вызове resolve() и reject() можно передать произвольные данные в зарегистрированные callback-функции для дальнейшей работы:

Кроме того, существуют ещё методы resolveWith() и rejectWith(), они позволяют изменять контекст вызываемых callback-функций (т.е. внутри них «this» будет смотреть на указанный контекст):

Отдельно отмечу, что если вы собираетесь передать Deferred объект «на сторону», чтобы «там» могли повесить свои обработчики событий, но не хотите потерять контроль, то возвращайте не сам объект, а результат выполнения метода promise() – фактически это будет искомый объект в режиме «read-only»:

доступные методы
будут недоступны

then, done, fail, always, pipe, progress, state и promise

resolve, reject, notify, resolveWith, rejectWith, и notifyWith

А ещё, кроме поведения «ждём чуда» с помощью Deferred можно выстраивать цепочки вызовов – «живые очереди»:

Подобное поведение мы уже реализовывали используя метод animate(), но нам же хочется найти свой путь:

До jQuery 1.8 тут шла речь о методе pipe(), а теперь о then()

В данном примере мы вызываем метод then(), которому скормлена callback-функция, которая должна возвращать объект Promise. Это необходимо для соблюдения порядка в очереди – попробуйте убрать в примере один «return», и вы заметите, что следующая анимация наступит не дождавшись завершения предыдущей:

На этом возможности Deferred ещё не завершились. Есть ещё связка методов notify() и progress() – первый шлёт послания в callback-функции, которые зарегистрированы с помощью второго. Приведу наглядный код для демонстрации (откройте консоль и посмотрите, что получается):


Испытайте всю мощь Deferred!

Создадим сервис для поиска картинок на сервисе Flickr. Начнем с объекта, который будет отвечать лишь за загрузку данных с Flickr:

Теперь запустим поиск и обработаем ответ сервера:

В результате у нас получился простенький поисковый сервис:

Last updated