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") });Если всё это перевести на человеческий язык, получится следующий сценарий:
если всё будет хорошо, тогда выполни вот эту функцию и выведи «
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(), но нам же хочется найти свой путь:
В данном примере мы вызываем метод then(), которому скормлена callback-функция, которая должна возвращать объект Promise. Это необходимо для соблюдения порядка в очереди – попробуйте убрать в примере один «return», и вы заметите, что следующая анимация наступит не дождавшись завершения предыдущей:
На этом возможности Deferred ещё не завершились. Есть ещё связка методов notify() и progress() – первый шлёт послания в callback-функции, которые зарегистрированы с помощью второго. Приведу наглядный код для демонстрации (откройте консоль и посмотрите, что получается):
Испытайте всю мощь Deferred!
Создадим сервис для поиска картинок на сервисе Flickr. Начнем с объекта, который будет отвечать лишь за загрузку данных с Flickr:
Теперь запустим поиск и обработаем ответ сервера:
В результате у нас получился простенький поисковый сервис:
Last updated