В коде на основе промисов обычно имеется много обратных вызовов, каждый из которых имеет отдельную область действия для переменных. Что, если вы хотите обмениваться данными между этими обратными вызовами? В этом сообщении блога описываются методы для этого.
Проблема
Следующий код иллюстрирует распространенную проблему с обратными вызовами Promise: переменная connection
(строка A) существует в одной области, но к ней нужно получить доступ в других областях (строка B, строка C).
db.open()
.then(connection => { // (A)
return connection.select({ name: 'Jane' });
})
.then(result => {
// Process result
// Use `connection` to make more queries (B)
})
···
.catch(error => {
// handle errors
})
.finally(() => {
connection.close(); // (C)
});
В этом коде мы используем Promise.prototype.finally()
, предлагаемую функцию ECMAScript. Он работает аналогично предложению finally
операторов try
.
Решение: побочные эффекты
Первое решение, которое мы рассмотрим, состоит в том, чтобы сохранить совместно используемое значение connection
в переменной в области видимости, окружающей все области обратного вызова (строка A).
let connection; // (A)
db.open()
.then(conn => {
connection = conn;
return connection.select({ name: 'Jane' });
})
.then(result => {
// Process result
// Use `connection` to make more queries (B)
})
···
.catch(error => {
// handle errors
})
.finally(() => {
connection.close(); // (C)
});
Из-за своего внешнего расположения connection
доступен в строке B и строке C.
Решение: вложенные области видимости
Синхронная версия исходного примера выглядит так:
try {
const connection = await db.open();
const result = await connection.select({ name: 'Jane' });
···
} catch (error) {
// handle errors
} finally {
connection.close();
}
В синхронном коде решение сделать connection
доступным в строке A обычно состоит в том, чтобы переместить объявление переменной в окружающую область:
const connection = await db.open();
try {
const result = await connection.select({ name: 'Jane' });
···
} catch (error) {
// handle errors
} finally {
connection.close();
}
Мы можем сделать что-то подобное с промисами — если мы вложим цепочки промисов:
db.open() // (A)
.then(connection => { // (B)
return connection.select({ name: 'Jane' }) // (C)
.then(result => {
// Process result
// Use `connection` to make more queries
})
···
.catch(error => {
// handle errors
})
.finally(() => {
connection.close();
});
})
Есть две цепочки промисов:
- Первая цепочка Promise начинается в строке A.
connection
— это асинхронно доставленный результатopen()
. - Вторая цепочка промисов вложена в обратный вызов
.then()
, начинающийся в строке B. Он начинается в строке C. Обратите внимание наreturn
в строке C, который гарантирует, что обе цепочки в конечном итоге будут правильно объединены.
Вложение дает всем обратным вызовам во второй цепочке доступ к connection
.
Вы могли заметить, что как в синхронном, так и в асинхронном коде синхронные исключения, созданные db.open()
, не будут обрабатываться catch
(предложение или обратный вызов). Promise.try()
показано, как исправить это для асинхронного кода. В коде синхронизации вы можете переместить db.open()
в предложение try
.
Решение: несколько возвращаемых значений
Следующий код демонстрирует другой подход к передаче данных между обратными вызовами. Однако это не всегда работает. В частности, вы не можете использовать его для текущего примера базы данных. Давайте посмотрим на пример, где это действительно работает.
Мы сталкиваемся с похожей проблемой: цепочка обещаний, в которой промежуточное значение intermediate
должно быть передано от обратного вызова, начинающегося в строке A, к обратному вызову, начинающемуся в строке B.
return asyncFunc1()
.then(result1 => { // (A)
const intermediate = ···;
return asyncFunc2();
})
.then(result2 => { // (B)
console.log(intermediate);
···
});
Мы решаем эту проблему, используя Promise.all()
для передачи нескольких значений из первого обратного вызова во второй:
return asyncFunc1()
.then(result1 => {
const intermediate = ···;
return Promise.all([asyncFunc2(), intermediate]); // (A)
})
.then(([result2, intermediate]) => {
console.log(intermediate);
···
});
Обратите внимание, что возврат массива в строке A не работает, потому что .then()
callback получит массив с обещанием и обычным значением. Promise.all()
использует Promise.resolve()
, чтобы гарантировать, что все элементы массива являются обещаниями, и выполняет свой результат с массивом их значений выполнения (если ни одно из обещаний не отклонено).
Одним из ограничений этого подхода является то, что вы не можете передавать данные в обратные вызовы .catch()
и .finally()
.
Наконец, этот метод выиграет от версии Promise.all()
, которая работает с объектами (а не только с массивами). Затем вы можете пометить каждое из нескольких возвращаемых значений.
Спасибо, что прочитали мою статью, и хлопайте, если она вам понравилась!
Пожалуйста, читайте и другие мои статьи: Функция-конструктор и «новое ключевое слово», Ключевое слово «это в методе объекта» и Функции — ключевая вещь. JavaScript» !