В этом руководстве вы узнаете о шаблоне цепочки промисов JavaScript, который связывает обещания для последовательного выполнения асинхронных операций.
Иногда вам нужно выполнить две или более связанных асинхронных операции, когда следующая операция начинается с результата предыдущего шага.
Пример:
- Во-первых, создайте новое обещание, которое разрешается в число 10 через 3 секунды:
let p = new Promise((resolve, reject) => { setTimeout(() => { resolve(10); }, 3 * 100); });
Обратите внимание, что функция setTimeout()
имитирует асинхронную операцию.
- Затем вызовите метод
then()
обещания:
p.then((result) => { console.log(result); return result * 2; });
Обратный вызов, переданный методу then()
, выполняется после разрешения промиса. В обратном вызове мы показываем результат промиса и возвращаем новое значение, умноженное на два( result*2
).
Поскольку метод then()
возвращает новое Promise
со значением, преобразованным в значение, вы можете вызвать метод then()
для возвращаемого Promise
следующим образом:
let p = new Promise((resolve, reject) => { setTimeout(() => { resolve(10); }, 3 * 100); }); p.then((result) => { console.log(result); return result * 2; }).then((result) => { console.log(result); return result * 3; });
Выход:
10 20
В этом примере возвращаемое значение первого метода then()
передается второму методу then()
. Вы можете последовательно вызвать метод then()
следующим образом:
let p = new Promise((resolve, reject) => { setTimeout(() => { resolve(10); }, 3 * 100); }); p.then((result) => { console.log(result); // 10 return result * 2; }).then((result) => { console.log(result); // 20 return result * 3; }).then((result) => { console.log(result); // 60 return result * 4; });
Выход:
10 20 60
То, как мы вызываем методы then()
, часто называют цепочкой промисов.
Следующая картинка иллюстрирует цепочку промисов:
Несколько обработчиков
Когда вы вызываете метод then()
несколько раз для промиса, это не цепочка промисов. Например:
let p = new Promise((resolve, reject) => { setTimeout(() => { resolve(10); }, 3 * 100); }); p.then((result) => { console.log(result); // 10 return result * 2; }) p.then((result) => { console.log(result); // 10 return result * 3; }) p.then((result) => { console.log(result); // 10 return result * 4; });
Выход:
10 10 10
В этом примере у нас есть несколько обработчиков для одного промиса. Эти обработчики не имеют отношений. Кроме того, они выполняются независимо и не передают результат от одного к другому, как в приведенной выше цепочке промисов.
На следующем рисунке показан промис, имеющее несколько обработчиков:
На практике вы редко будете использовать несколько обработчиков для одного промиса.
Возвращение обещания
Когда вы возвращаете значение в методе then()
, метод then()
возвращает новое Promise
, которое немедленно преобразуется в возвращаемое значение.
Кроме того, вы можете вернуть новое обещание в методе then()
, например:
let p = new Promise((resolve, reject) => { setTimeout(() => { resolve(10); }, 3 * 100); }); p.then((result) => { console.log(result); return new Promise((resolve, reject) => { setTimeout(() => { resolve(result * 2); }, 3 * 1000); }); }).then((result) => { console.log(result); return new Promise((resolve, reject) => { setTimeout(() => { resolve(result * 3); }, 3 * 1000); }); }).then(result => console.log(result));
Выход:
10 20 60
Этот пример показывает 10, 20 и 60 через каждые 3 секунды. Этот шаблон кода позволяет выполнять некоторые задачи последовательно.
Следующее изменило приведенный выше пример:
function generateNumber(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(num); }, 3 * 1000); }); } generateNumber(10) .then((result) => { console.log(result); return generateNumber(result * 2); }) .then((result) => { console.log(result); return generateNumber(result * 3); }) .then((result) => console.log(result));
Синтаксис
Иногда у вас есть несколько асинхронных задач, которые вы хотите выполнить последовательно. Кроме того, вам нужно передать результат предыдущего шага следующему. Синтаксис:
step1() .then(result => step2(result)) .then(result => step3(result)) ...
Если вам нужно передать результат от предыдущей задачи к следующей без передачи результата, вы используете этот синтаксис:
step1() .then(step2) .then(step3) ...
Предположим, вы хотите последовательно выполнить следующие асинхронные операции:
- Во-первых, получить пользователя из базы данных.
- Во-вторых, получить услуги выбранного пользователя.
- В-третьих, рассчитать стоимость услуги пользователя.
Следующие функции иллюстрируют три асинхронных операции:
function getUser(userId) { return new Promise((resolve, reject) => { console.log('Get the user from the database.'); setTimeout(() => { resolve({ userId: userId, username: 'admin' }); }, 1000); }) } function getServices(user) { return new Promise((resolve, reject) => { console.log(`Get the services of ${user.username} from the API.`); setTimeout(() => { resolve(['Email', 'VPN', 'CDN']); }, 3 * 1000); }); } function getServiceCost(services) { return new Promise((resolve, reject) => { console.log(`Calculate the service cost of ${services}.`); setTimeout(() => { resolve(services.length * 100); }, 2 * 1000); }); }
Следующий код использует обещания для сериализации последовательностей:
getUser(100) .then(getServices) .then(getServiceCost) .then(console.log);
Выход:
Get the user from the database. Get the services of admin from the API. Calculate the service cost of Email,VPN,CDN. 300
Обратите внимание, что ES2017 представил async
/ await
, который помогает вам писать код, который чище, чем при использовании метода цепочки промисов.