async/await - это синтаксис, который делает работу с Promise проще для чтения. По сути это “удобная обертка” над then/catch, но с более прямым стилем кода.
1) async: функция, которая возвращает Promise
Если перед функцией стоит async, она всегда возвращает Promise.
async function getValue() {
return 123;
}
getValue().then((value) => {
console.log(value); // 123
});
Даже если внутри возвращается обычное число, снаружи это будет Promise.
2) await: дождаться результата Promise
await можно использовать только внутри async-функции. Он “останавливает” выполнение этой функции до тех пор, пока Promise не выполнится.
function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function run() {
console.log("A");
await wait(300);
console.log("B");
}
run();
Важный нюанс: await не блокирует весь браузер. Он приостанавливает только выполнение текущей async-функции, а страница продолжает работать.
3) Сравнение с then: один и тот же смысл
Через then:
function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
wait(200).then(() => {
console.log("done");
});
Через await:
function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function run() {
await wait(200);
console.log("done");
}
run();
4) Обработка ошибок: try/catch
Если Promise будет rejected, await выбросит ошибку. Ее ловят через try/catch.
async function run() {
try {
const value = await Promise.reject(new Error("Fail"));
console.log(value);
} catch (e) {
console.log(e.name);
console.log(e.message);
}
}
run();
5) Пример с fetch: читаемый шаблон
async function loadJSON(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP " + response.status);
}
const data = await response.json();
return data;
}
loadJSON("/api/data")
.then((data) => {
console.log(data);
})
.catch((e) => {
console.log(e.message);
});
Внутри loadJSON - стиль async/await, снаружи можно продолжать использовать then/catch.
6) Несколько await подряд: последовательность
Если написать await один за другим, операции будут идти по очереди.
async function run() {
const a = await loadJSON("/api/a");
const b = await loadJSON("/api/b");
console.log(a, b);
}
Такой вариант логичный, если второй запрос зависит от первого.
7) Параллельные операции: Promise.all
Если зависимости нет, чаще выгоднее запускать параллельно.
async function run() {
const p1 = loadJSON("/api/a");
const p2 = loadJSON("/api/b");
const [a, b] = await Promise.all([p1, p2]);
console.log(a, b);
}
Здесь оба запроса стартуют сразу, а await Promise.all ждет, пока завершатся оба.
8) Типовые ошибки
1) await вне async
const data = await fetch("/api/data");
Так нельзя. Нужна async-функция:
async function run() {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
}
run();
2) Забыть await
async function run() {
const response = fetch("/api/data");
console.log(response.ok);
}
response здесь Promise, у него нет ok. Нужен await:
async function run() {
const response = await fetch("/api/data");
console.log(response.ok);
}
3) try/catch не ловит ошибки без await
async function run() {
try {
fetch("/api/data");
} catch (e) {
console.log("not caught");
}
}
fetch возвращает Promise, ошибка появится позже. Чтобы ловить, нужно await:
async function run() {
try {
await fetch("/api/data");
} catch (e) {
console.log("caught");
}
}
Что стоит запомнить
- async-функция всегда возвращает Promise.
- await “ждет” Promise и возвращает его результат.
- await не блокирует страницу, он приостанавливает только текущую async-функцию.
- Ошибки с await ловят через try/catch.
- Для параллельных задач удобно Promise.all.