Промисы
Результат таймера можно использовать только внутри колбэка. Отдать его наружу через return не получится: когда функция заканчивается, результата ещё нет. Давайте посмотрим:
function loadName() {
setTimeout(() => {
return "Ann";
}, 100);
}
console.log(loadName()); // undefinedreturn "Ann" выходит из колбэка, а не из loadName, и это происходит через 100 миллисекунд. Сама loadName до конца доходит сразу и без return, поэтому возвращает undefined.
Представьте кафе, где заказ готовят не сразу. На кассе вам отдают не блюдо, а электронный пейджер. С ним можно сесть за столик и заняться своим. Когда блюдо будет готово, пейджер загорится. Если блюдо закончилось, он покажет, что заказ отменён.
Объект на будущее
Промис — это объект для результата, который будет готов позже. Он и есть такой пейджер: функция возвращает промис сразу, а значение попадает в него потом:
function loadName() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Ann");
resolve("Bob");
}, 100);
});
}
loadName().then((name) => {
console.log(name);
});
// Annnew Promise принимает функцию с двумя параметрами и запускает её сразу. Параметры получают две функции:
- вызов
resolve(value)выполняет промис со значением; - вызов
reject(reason)отклоняет его с причиной, обычно объектомError.
Три состояния
У промиса три состояния:
pending— промис ждёт, пейджер молчит;fulfilled— промис выполнен со значением, блюдо готово;rejected— промис отклонён с причиной, заказ отменён.
Сначала промис pending. Потом он становится fulfilled со значением или rejected с причиной и больше не меняется.
В примере промис остаётся pending 100 миллисекунд, пока не сработает таймер. Второй вызов resolve("Bob") ничего не делает: промис уже выполнен со значением "Ann".
Здесь аналогия с пейджером перестаёт работать. Пейджер в кафе потом отдают следующему гостю, а промис не переиспользуют: он выполняется или отклоняется один раз и навсегда.
Этот вопрос часто задают на собеседовании: что такое промис и какие у него состояния? Хороший ответ называет все три состояния и добавляет, что после pending состояние меняется только один раз.
then и catch
Метод then добавляет колбэк для значения, а catch — для причины отказа. Программу они не останавливают: только запоминают колбэк, и строки после них выполняются сразу.
function checkAge(age) {
return new Promise((resolve, reject) => {
if (age >= 0) {
resolve(age);
} else {
reject(new Error("bad age"));
}
});
}
checkAge(-1)
.then((age) => age + 1)
.then((next) => console.log(next))
.catch((e) => console.log(e.message));
// bad ageЦепочку вызовов, как в уроке о map и filter, можно разбить на строки: строка, которая начинается с точки, продолжает предыдущую. Четыре строки от checkAge(-1) до catch — одно выражение.
Каждый then возвращает новый промис со значением, которое вернул его колбэк. Так шаги выстраиваются в цепочку: следующий then получает то, что вернул предыдущий.
Разберём по шагам, что происходит при checkAge(-1):
- Число
-1меньше нуля, поэтомуcheckAgeотклоняет промис с ошибкойbad age. - Первый
thenпропускает свой колбэк и передаёт отказ дальше по цепочке. - Второй
thenтоже пропускает свой колбэк. catchполучает ошибку и печатаетbad age.
С checkAge(20) всё иначе: промис выполнится со значением 20, первый then вернёт 21, второй напечатает 21, а catch не вызовется. Одного catch в конце хватает на всю цепочку.
Причина отказа приходит в колбэк catch параметром e, как в try…catch, и e.message даёт её текст. Отказ, который никто не поймал, Node.js считает ошибкой и останавливает программу. Браузер пишет его в консоль.
Частая ошибка
С промисом обращаются как с готовым значением:
function loadName() {
return new Promise((resolve) => {
resolve("ann");
});
}
const name = loadName();
console.log(name.toUpperCase()); // ✗TypeError: name.toUpperCase is not a functionВ name лежит промис, а не строка, и метода toUpperCase у него нет. Это всё равно что пытаться съесть пейджер. Значение достают в колбэке: loadName().then((name) => console.log(name.toUpperCase())) печатает ANN.
Главное
- Промис — объект для результата, который будет готов позже; функция возвращает его сразу.
- Промис
pending, пока не станетfulfilledсо значением илиrejectedс причиной, и потом больше не меняется. thenиcatchтолько добавляют колбэки, а каждыйthenвозвращает новый промис для следующего шага цепочки.- Отказ пропускает все
thenдо ближайшегоcatch.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.