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
0Tweet about this on Twitter
Twitter

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

Ваш адрес email не будет опубликован.

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.

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