Передача данных между колбеками в Promise.

Anrew Muntian
Aug 9, 2017 · 3 min read

Вольный перевод статьи Dr. Axel Rauschmayer Passing data between Promise callbacks

В коде, основанном на Promise, обычно очень много колбеков, у каждого из которых своя отдельная область видимости переменных (ориг.: variables scope). Но как быть, если нужно получить доступ к данным из одного колбека в другом. Эта статья описывает техники, позволяющие решить эту проблему.

1. Описание проблемы

Код, приведенный ниже, иллюстрирует распространенную проблему, возникающую при использовании колбеков в Promise: переменная connection (строка А) существует в одной области видимости, но нужно получить доступ к ней из других (строки B и С).

В этом коде используется метод Promise.prototype.finally(), который пока находится в статусе proposed в стандарте ECMAScript. Он используется как finally в конструкции:

2. Подход: использовать побочные эффекты (ориг.: side effects)

Первое решение, которое мы рассмотрим, заключается в том, что мы будем хранить данные, которые нужно сделать общедоступными, в переменной (connection) в области видимости, к которой есть доступ у всех нуждающихся 🙂 Т.е. это будет родительская область видимости (строка А).

Благодаря тому, что данные сохранены во внешней области видимости, мы имеем к ним доступ в стоках B и С.

3. Подход: использовать вложенные области видимости

Если переписать оригинальный пример, описывающий проблему (см. п. 1), в синхронном стиле, то он будет выглядеть вот так:

С таким подходом решить нашу проблему довольно просто: нужно вынести определение переменной connection во внешнюю область видимости.

Мы можем сделать что-то подобное и при использовании Promise, если вложим одну цепочку Promise в другую:

Тут две цепочки Promiseов:

  • первая цепочка стартует на строке А. connection это результат операции open(), которая является асинхронной.
  • вторая цепочка является вложенной в колбек блока then(), который находится в строке В. Она стартует со строки С. Обратите внимание на return в строке С, который гарантирует, что обе цепочки будут в итоге объединены корректно, т.е. весь код будет выполнен и выполнен в правильной последовательности.

Вложенность позволяет всем колбекам во второй цепочке Promise иметь доступ к переменной connection.

Вы также могли заметить, что в обоих версиях кода (синхронной и асинхронной) синхронное исключение, которое может быть выброшено методом db.open() не будет обработано блоком/колбеком catch. В статье, которая выйдет в скором будущем, будет описана техника, которая поможет решить эту проблему для асинхронной версии кода. Для синхронной версии можно просто вынести db.open() в блок try.

4. Подход: возвращать несколько значений

Следующий код демонстрирует еще один подход передачи данных между колбеками в Promise. Тем не менее такой подход работает не всегда. Если конкретно, то вы не можете использовать его для показанного выше примера с методом db.open() из mongodb. Давайте взглянем на пример, в котором такой подход можно использовать.

Мы сталкиваемся с аналогичной проблемой: имеем цепочку Promise, в которой находится промежуточное значение intermediate, которое нужно передать из колбека, расположенного в строке А, в колбек, расположенный в строке В.

Мы решим данную задачу, используя Promise.all() для передачи нескольких параметров из первого колбека во второй:

Обратите внимание, что если просто вернуть массив в строке А (return [asyncFunc2(), intermediate]), то код отработает не совсем так, как ожидается.

Какого результата мы ожидаем? Мы ожидаем, что колбек блока .then() в строке В в качестве аргумента получит массив со значениями 'apple' и 42, но на деле получится объект типа Promise в состоянии pending и 42. Все потому, что Promise, который является результатом выполнения функции asyncFunc2 не успел выполнится. Он выполнится, но когда-нибудь потом, когда нам совсем этого не нужно.

Но если мы используем Promise.all(), то он, в свою очередь, использует метод Promise.resolve() чтобы:

  1. гарантировать, что все, переданные в него аргументы являются объектами типа Promise.
  2. в качестве результата своего выполнения вернуть массив, заполненный результатами выполнения переданных в качестве аргументов объектов типа Promise (если ни один из них не был переведен в состояние rejected). Если выполнить Promise.resolve(42), то в результате получим объект типа Promise в состоянии fulfilled. То, что нужно! (прим.пер.: довольно запутанное предложение, поэтому почитайте пожалуйста более подробно о Promise тут или тут)

Хотя у такого подхода есть ограничение. Таким способом не получится передать данные в колбеки блоков .catch() и .finally().

5. Что еще почитать


Originally published at muntianblog.wordpress.com on August 9, 2017.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade