В коде на основе промисов обычно имеется много обратных вызовов, каждый из которых имеет отдельную область действия для переменных. Что, если вы хотите обмениваться данными между этими обратными вызовами? В этом сообщении блога описываются методы для этого.

Проблема

Следующий код иллюстрирует распространенную проблему с обратными вызовами 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» !