Передача данных между колбеками в Promise.
Вольный перевод статьи 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() чтобы:
- гарантировать, что все, переданные в него аргументы являются объектами типа
Promise. - в качестве результата своего выполнения вернуть массив, заполненный результатами выполнения переданных в качестве аргументов объектов типа
Promise(если ни один из них не был переведен в состояниеrejected). Если выполнитьPromise.resolve(42), то в результате получим объект типаPromiseв состоянииfulfilled. То, что нужно! (прим.пер.: довольно запутанное предложение, поэтому почитайте пожалуйста более подробно оPromiseтут или тут)
Хотя у такого подхода есть ограничение. Таким способом не получится передать данные в колбеки блоков .catch() и .finally().
5. Что еще почитать
Originally published at muntianblog.wordpress.com on August 9, 2017.
