Deferred и promise в jQuery, или как избавиться от вложенных коллбэков.

Что плохого в коллбэках (callback)

В колбэках, в общем-то, ничего плохого нет. Проблема может возникнуть, однако, когда вы хотите, чтобы один колбэк вызывался строго после выполнения другого. Например, вы отправляете AJAX-запрос, и только когда будет получен ответ, вы хотите отправить еще один запрос с другой колбэк-функцией. Отлично. Но что если у вас три, четыре, десять таких пар запрос-колбэк? Что, если вы хотите отправить три запроса на сервер, а колбэк-функцию вызвать, только когда все три вернут что-то с сервера? Всё это может привести к печально выглядящему коду:

Пример взят с сайта, который так и озаглавлен — ад колбэков (callbackhell.com). Чтобы решить озвученные (а также более сложные и менее очевидные) задачи, была придумана концепция обещаний (promises).

Что такое deferred и promise

Концепция обещаний позволяет обращаться с асинхронными запросами, будто они синхронные. Обещания (promises) — это значения, которые еще только должны быть вычислены в будущем. Однако описывать то, что с ними  нужно делать, когда они будут вычислены, можно заранее. Обычный JavaScript поддерживает эту концепцию, начиная со спецификации ECMAScript 2015. Подробнее ознакомиться с этой реализацией можно на сайте консорциума Mozilla MDN. Поскольку стандарт пока используется достаточно ограниченно, лучше воспользоваться реализацией этой концепции в одной из библиотек. Я остановлюсь в данной статье на jQuery, как самой популярной.

Лучше всего концепция объяснена у Криса Уэбба , я возьму с его блога несколько примеров. В jQuery введена конструкция Deferred-объекта. Этот объект представляет асинхронное действие, которое еще не выполнено (с отложенным выполнением). Каждый deferred-объект имеет внутри себя promise-объект, который представляет значение, которое пока неизвестно.  У обещаний, в свою очередь, есть состояния (resolved, failed) и обработчики, которые определяют, что делать в ситуации смены состояния.

Канонический пример, использующий Deferred и содержащееся в нем обещание:

Здесь мы определили deferred-объект, затем через метод done содержащегося в нем обещания назначаем функцию, выводящую в окне будущее значение, которое нам будет известно, когда deferred будет выполнен (resolved). Наконец, вручную выполняем deferred со значением «hello world». Предсказуемо, эта строка выводится во всплывающем окне.

Основные методы Promise и Deferred

Самый простой реалистичный сценарий использования обещания:

Метод jQuery ajax возвращает обещание. В примере через метод done мы задаем функцию, которая будет выполнена, если запрос пройдет успешно (не вернет ошибку).

deferred-Объект начинает свой путь в подвешенном состоянии (pending). Изменить его состояние можно функциями resolve() и reject(). Тогда он перейдет в статус, соответственно, resolved (разрешенный, успешно отработавший) и rejected (отмененный, отработавший с ошибкой). В конечный статус он может перейти лишь один раз.

Обещание Promise — это неизменяемый (immutable) объект. То есть что-то менять можно только в Deferred, но не в Promise.

Promise предоставляет следующие функции:

state() — получить состояние объекта

done() — позволяет привязать обработчик события смены статуса на resolved

fail() — привязывает обработчик события смены статуса на rejected

then() — объединяет возможности done и fail (может привязать сразу два обработчика) + позволяет выстраивать обработчики в цепочки (об этом далее по тексту)

С помощью конструкции $.when() можно объединять условия вызова обработчиков. Например, если мы отправили на сервер два асинхронных запроса и хотим вызвать обработчик только после того, как оба запроса вернут данные.

Интересная особенность Deferred — если он был выполнен (resolved) до того, как обработчик на это событие был назначен, то этот обработчик выполнится немедленно:

Примеры использования

Основные варианты использования крутятся вокруг трех вещей (взято из Promise & Deferred Objects in JavaScript Pt.2: in Practice):

Несколько обработчиков на одно событие

Обработчик на выполнения двух задач сразу

Последовательное выполнение асинхронных задач

Когда одна задача выполнена, начинает выполняться вторая

Практическое применение этих шаблонов может быть в AJAX-запросах, анимации, синхронизации параллельных задач при помощи $.when(), развязывании событий и программной логики и т.п.

Особенности работы then()

  • .then() всегда возвращает новое обещание (Promise)
  • .then() ожидает функцию в качестве параметра

Если в .then() не передана функция:

  • новое обещание будет повторять поведение исходного обещания (что означает, что оно будет немедленно выполнено/отклонено)
  • ввод внутри .then() будет выполнен, но проигнорирован .then()

если в .then() передана функция, которая возвращает Promise-объект:

  • новое обещание будет иметь то же поведение, что и возвращаемое обещание
  • если в .then() передана функция, которая возвращает значение, то это значение становится значением нового объекта

А зачем нам использовать jQuery?

В данный момент обещания уже входят в стандарт ECMA и реализованы нативно почти во всех современных браузерах. При этом реализована спецификация Promise/A. Многие библиотеки, реализующие сходную концепцию, совместимы с этой спецификацией, однако, это не случай jQuery, в котором всё сделано не так. Если вы активно пользуетесь jQuery, но предпочитаете вместо Deferred-объектов использовать нативные Promise для JavaScript, как это рекомендовано, например, в статье Джейка Арчибальда, то в этой же статье дается совет преобразовывать полученные из методов jQuery обещания к стандартному объекту Promise так скоро, как возможно. Например, так:

$.ajax() возвращает Deferred-объект jQuery, но этот объект легко приводится к стандартному Promise указанным способом.

Еще по теме и использованные материалы (англ.)

Поделиться: Share on LinkedIn
Linkedin
Share on VK
VK
Share on Facebook
Facebook
0Share on Google+
Google+
0Tweet about this on Twitter
Twitter

Оставить комментарий

Ваш e-mail не будет опубликован.

Яндекс.Метрика