Это основано на сообщении Роба Использование обещаний и async / await в Meteor и посвящено тому, как мы можем использовать обещания на клиенте - и почему мы должны это делать. Если вы не читали исходную статью, сейчас самое подходящее время!

Многие пакеты npm теперь поставляются с API на основе Promise, и мы увидели, что использование Promises в коде сервера Meteor может значительно упростить их внедрение в наши проекты Meteor. В частности, при использовании async и await наш код больше похож на кодировку «стиля синхронизации» на основе Fiber, к которой мы привыкли.

Клиент Meteor не использует Fibers и всегда требовал, чтобы мы использовали асинхронное кодирование JavaScript с использованием обратных вызовов. Тем не менее, это часто сбивает с толку, когда описывается цель Meteor - изоморфизм: один и тот же код на клиенте и сервере. Теперь, с помощью Promises и async / await, наконец-то можно значительно приблизиться к этой цели.

Мы видели, что методы Meteor на сервере уже совместимы с Promise. Однако на момент написания, хотя промисы и async / await поддерживаются на клиенте, Meteor не предоставляет никакого удобного способа их использования - особенно для борьбы с «адом обратных вызовов».

Итак, в чем мы видим потенциал для улучшения? Что ж, в любое время, когда документация по клиентскому методу требует, чтобы мы использовали обратный вызов для получения результата (любое асинхронное событие). Очевидные кандидаты - setTimeout, setInterval, Meteor.call, Meteor.apply и HTTP.

Менее очевидны те, у которых есть реактивный компонент: Meteor.subscribe, ReactiveVar, ReactiveDict, Session и курсоры. Однако ни один из них не идеален для использования с обещаниями, поскольку обещание предназначено для возврата одного значения. Реактивные компоненты могут возвращать множество значений с течением времени. Наблюдаемые объекты - лучший образец для этого, но это отдельная статья в блоге! Между тем, Meteor предоставляет pub / sub и Tracker, что дает нам минимально инвазивные способы их решения.

Из очевидных кандидатов давайте начнем с Meteor.call и рассмотрим общее требование вызова метода для получения информации из базы данных MySQL. Мы уже видели пример этого в моей оригинальной статье. И снова:

Это не так уж и плохо - один вызов с его обратным вызовом, в котором мы устанавливаем ReactiveVar, как только получаем ответ.

Если бы мы могли переписать это так, чтобы использовать async и await, вот что мы бы хотели увидеть:

К сожалению, на клиенте форма Meteor.call без обратного вызова не возвращает ничего полезного, не говоря уже об обещании, хотя был пиар для этого, пока все не усложнилось.

Однако один из способов заставить такой код работать на клиенте - это добавить некоторый шаблонный код.

Помните, в былые времена мы использовали для преобразования обратных вызовов на сервере с помощью Meteor.wrapAsync, прежде чем у нас был async / await? Что ж, этот код выше оборачивает форму обратного вызова Meteor.call в обещание. Он некрасивый (даже не такой красивый, как wrapAsync), не справляется с параметрами (мы все это исправим дальше), но он позволяет написать вот что:

callWithPromise - полезное упражнение для понимания того, как обернуть обратный вызов в обещание, и продемонстрировать, как обещания могут использоваться с Meteor.call на клиенте. Однако есть некоторые пакеты для атмосферы, которые объединяют это для нас (и делают это намного лучше). Это колесо уже несколько раз изобретали заново - давайте не будем делать этого снова!

Я собираюсь использовать deanius:promise (в котором есть ряд других полезных оболочек Promise, в том числе для HTTP), но вы также можете использовать grove:call-async, okgrow:promise или didericis:callpromise-mixin (если вам нужен удобный способ добавления обещаний к проверенным методам).

После выполнения meteor add deanius:promise я могу переписать свой client/main.js как:

Между прочим, документацию для deanius:promise можно получить, клонировав meteor-promise docs (приложение Meteor) и запустив его локально.

Давайте посмотрим на более сложный (т.е. чрезвычайно надуманный) пример - на самом деле мы бы не сделали этого таким образом, но он проиллюстрирует проблемы написания кода, имеющего много последовательных асинхронных вызовов. Мы изменим клиентский HTML-код и код шаблона, чтобы показать нам личность текущего носителя кольца, а затем:

  1. Мы вызовем метод для получения (и визуализации) текущего списка хоббитов.
  2. Мы вызовем метод, чтобы установить Бильбо в качестве носителя кольца.
  3. Получим (и визуализируем) результат.
  4. Мы сделаем Фродо хранителем кольца.
  5. Получим (и визуализируем) результат.

Чтобы у нас было время увидеть изменения в браузере, мы добавим задержку для вызовов с помощью setTimeout.

Наш серверный код, измененный, чтобы также возвращать текущий носитель кольца:

Обновленный шаблон, чтобы показать нам, кто носит кольцо:

И соответствующий клиентский код:

Код клиента очень труден для понимания. Более того, если бы мы назначили кольцо хоббитам в полной последовательности - Смеаголу, Бильбо, Фродо, Сэму, Фродо, Смеаголу, отступы пошли бы прямо за пределы Средиземья! (Обратите внимание, я исключил Деагола из приведенной выше последовательности - его владение было довольно ... недолгим.)

Давайте перепишем клиентский код с помощью async / await:

Я вручную написал небольшую служебную функцию на основе Promise, чтобы позволить потоку спать на несколько миллисекунд. Его можно заменить пакетом npm, например one of these.

Надеюсь, вы согласитесь, что теперь клиентский код намного легче читать. Даже без аннотации легко увидеть, что должно произойти.

В заключение

Обещания - отличный способ создания приложений Meteor, отвечающих требованиям завтрашнего дня. Внедрение async и await ES7 - это очень простой способ улучшить читаемость и переносимость наших приложений Meteor на сервере - и тем более на клиенте, где «ад обратного вызова» можно практически исключить.