Promise - это объект, который представляет результат асинхронной операции: он появится позже (успех) или операция завершится ошибкой.
У Promise есть три основных состояния:
- pending - в процессе
- fulfilled - успешно
- rejected - с ошибкой
В повседневном коде важнее другое: как правильно читать результат и ловить ошибки.
1) then: получить успешный результат
then принимает функцию, которая получит результат, когда Promise выполнится успешно.
Promise.resolve("OK").then((value) => {
console.log(value); // "OK"
});
2) catch: обработать ошибку
catch срабатывает, если Promise завершился с ошибкой.
Promise.reject(new Error("Boom")).catch((error) => {
console.log(error.name); // "Error"
console.log(error.message); // "Boom"
});
3) finally: выполнить в любом случае
finally запускается и при успехе, и при ошибке. Обычно там убирают loading, закрывают ресурсы, включают кнопки обратно.
Promise.resolve("OK")
.then((value) => {
console.log(value);
})
.finally(() => {
console.log("done");
});
4) Цепочки then: шаги один за другим
Главная сила Promise - цепочки. Если из then вернуть значение, оно станет результатом следующего then.
Promise.resolve(2)
.then((n) => n * 3)
.then((n) => n + 1)
.then((result) => {
console.log(result); // 7
});
5) Возврат Promise из then
Если вернуть Promise, цепочка подождет его завершения.
function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
wait(200)
.then(() => {
console.log("step 1");
return wait(200);
})
.then(() => {
console.log("step 2");
});
6) Ошибки: как они “падают” в catch
Если внутри then произойдет ошибка, она автоматически перейдет в catch.
Promise.resolve("data")
.then(() => {
throw new Error("Broken");
})
.catch((error) => {
console.log(error.message); // "Broken"
});
То же самое, если вернулся rejected Promise.
Promise.resolve("data")
.then(() => Promise.reject(new Error("Rejected")))
.catch((error) => {
console.log(error.message); // "Rejected"
});
7) catch можно ставить в конце
Чаще всего catch ставят в конце цепочки, чтобы ловить все ошибки сразу.
Promise.resolve(10)
.then((x) => x * 2)
.then((x) => {
if (x > 10) {
throw new Error("Too big");
}
return x;
})
.then((x) => {
console.log(x);
})
.catch((error) => {
console.log(error.message);
})
.finally(() => {
console.log("finish");
});
8) Продолжить цепочку после catch
catch тоже возвращает Promise. Если из catch вернуть значение, цепочка продолжится как успешная.
Promise.reject(new Error("Fail"))
.catch(() => {
return 0;
})
.then((value) => {
console.log(value); // 0
});
Если внутри catch снова бросить ошибку, цепочка останется с ошибкой.
Promise.reject(new Error("Fail"))
.catch(() => {
throw new Error("Still fail");
})
.catch((error) => {
console.log(error.message); // "Still fail"
});
9) Типовые ловушки
1) Забыть return в then
Если внутри then нужно передать значение дальше, оно должно быть возвращено.
Promise.resolve(5)
.then((x) => {
x * 2;
})
.then((value) => {
console.log(value); // undefined
});
Правильно:
Promise.resolve(5)
.then((x) => x * 2)
.then((value) => {
console.log(value); // 10
});
2) Пытаться ловить ошибки через try/catch вокруг Promise
try/catch работает только для синхронного кода. Для Promise нужен catch или async/await с try/catch.
try {
Promise.reject(new Error("Fail"));
} catch (e) {
console.log("not works");
}
Здесь catch не поймает ошибку Promise.
Что стоит запомнить
- then - обработка успешного результата.
- catch - обработка ошибки (в том числе ошибок внутри then).
- finally - выполняется всегда, удобно для “убрать загрузку”.
- Возврат значения из then передает его дальше по цепочке.
- Возврат Promise из then заставляет цепочку ждать.
- Чтобы передать значение дальше, в then нужен return.