Интервью-вопрос
Что такое await
await помогает дождаться Promise внутри async-кода и получить результат в линейной форме. Важно помнить, что он не блокирует весь браузер. Ошибки нужно обрабатывать, а независимые операции лучше запускать параллельно.
- Добавлен
- Редакция
Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.
Мини-квиз
Проверка перед разбором
Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.
Вопрос 1 из 60 правильно
Разбор
Разобраться, а не зазубрить
Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.
Базовая идея
await помогает писать асинхронный код линейно. Вы ставите его перед Promise. Продолжение текущей async-функции ставится на паузу. Когда Promise завершается, код идет дальше со следующей строки.
async function loadUser() {
const response = await fetch("/api/user");
const user = await response.json();
return user;
}Важно сказать точно: паузится не весь браузер и не весь JavaScript. Паузится продолжение этой функции. Поэтому пользователь может кликать, другие обработчики могут выполняться. А тяжелый синхронный цикл все равно заблокирует интерфейс, даже если рядом есть await.
Что происходит с результатом и ошибкой
Если Promise успешно завершился, await возвращает его значение. Если Promise завершился с ошибкой, await выбрасывает исключение в той строке, где он стоит. Это удобно, потому что ошибки можно ловить обычным try/catch.
async function loadSettings() {
try {
const response = await fetch("/api/settings");
if (!response.ok) {
throw new Error(`Bad status: ${response.status}`);
}
return await response.json();
} catch (error) {
showError("Не удалось загрузить настройки");
throw error;
}
}Практический вывод для фронтенда простой. После ошибки мало залогировать проблему. Нужно вернуть UI из состояния загрузки, показать понятное сообщение и не использовать неполные данные как успешные.
Как выбирать между await, Promise.all и allSettled
Сам await не решает, должны операции идти последовательно или параллельно. Это задает структура вашего кода. Если второй запрос зависит от результата первого, последовательные await нормальны. Если запросы независимы, последовательность часто добавляет лишнюю задержку.
Как выбрать форму ожидания
Используйте await и обработайте ошибку рядом или выше.Запустите их сразу и ждите через Promise.all.Рассмотрите Promise.allSettled и отдельно обработайте rejected-результаты.Последовательные await здесь уместны. Данные действительно зависят друг от друга.Плохой вариант для независимых запросов:
async function loadPage() {
const profile = await fetch("/api/profile").then((r) => r.json());
const settings = await fetch("/api/settings").then((r) => r.json());
return { profile, settings };
}Здесь второй запрос стартует только после первого. Если каждый отвечает по 500 мс, пользователь может ждать около секунды вместо половины секунды.
Более безопасный вариант:
async function loadPage() {
const [profileResponse, settingsResponse] = await Promise.all([
fetch("/api/profile"),
fetch("/api/settings"),
]);
if (!profileResponse.ok || !settingsResponse.ok) {
throw new Error("Не удалось загрузить данные страницы");
}
const [profile, settings] = await Promise.all([
profileResponse.json(),
settingsResponse.json(),
]);
return { profile, settings };
}Здесь запросы стартуют одновременно. Но помните про trade-off: Promise.all отклонится при первой ошибке. Если нужно показать частичные данные, рассмотрите Promise.allSettled. Потом отдельно решите, что делать с упавшими частями.
Главные ловушки на фронтенде
- 1Запустить нужные Promise
- 2Дождаться результата через await
- 3Проверить response.ok, если это fetch
- 4Обновить UI или показать ошибку
- 1Поставить await перед каждым независимым запросом
- 2Не обработать rejected Promise
- 3Считать любой response успешным
- 4Оставить экран в вечном loading
Частая ловушка с fetch: await дождался ответа, но это еще не значит, что ответ успешен для приложения. HTTP 404 или 500 обычно не отправят код в catch. Вы получите Response и должны сами проверить response.ok.
Еще одна ловушка связана с возвращаемым значением. async-функция всегда возвращает Promise. Даже если внутри написано return 42, снаружи это будет Promise, который resolved со значением 42. Поэтому вызывающий код тоже должен использовать await, .then или передать Promise дальше осознанно.
Await в React-компоненте
await не отменяет запрос и не гарантирует, что результат все еще нужен текущему экрану. Плохой вариант часто выглядит безопасно, потому что код читается сверху вниз:
useEffect(() => {
async function loadUser() {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
setLoading(false);
}
loadUser();
}, [userId]);Что сломается: если userId изменится быстро, старый запрос может завершиться позже нового и перезаписать карточку пользователя. Если компонент уже ушел с экрана, результат больше не нужен, но код все равно пытается менять состояние. Еще здесь нет обработки error. Поэтому UI может навсегда остаться в loading.
Безопаснее привязать запрос к жизненному циклу эффекта:
useEffect(() => {
const controller = new AbortController();
let active = true;
async function loadUser() {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`Bad status: ${response.status}`);
}
const data = await response.json();
if (active) {
setUser(data);
}
} catch (error) {
if (error.name === "AbortError") {
return;
}
if (active) {
setError("Не удалось загрузить пользователя");
}
} finally {
if (active) {
setLoading(false);
}
}
}
loadUser();
return () => {
active = false;
controller.abort();
};
}, [userId]);На интервью не обязательно писать весь код. Достаточно сказать: в UI вы думаете не только про await, но и про отмену запроса, устаревший ответ, loading, error и cleanup.
Что сказать коротко на интервью
Хорошая короткая формулировка может звучать так:
await ждет завершения Promise внутри async-кода и позволяет получить результат как обычное значение. Если Promise rejected, await выбрасывает ошибку. Поэтому я обрабатываю ее через try/catch или отдаю выше. При этом await не блокирует весь браузер, а только продолжение текущей функции. Для независимых операций я обычно запускаю Promise параллельно через Promise.all.
Этого достаточно для базового ответа. Если спросят глубже, добавьте про top-level await в ES modules, проверку response.ok у fetch и разницу между последовательным и параллельным ожиданием.
Частые ошибки
Где обычно ошибаются
Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.
- 1
Считать await блокировкой всего браузера
awaitставит на паузу текущуюasync-функцию. Он не превращает асинхронный код в синхронную блокировку main thread. Если сказать это неточно, у вас могут уточнить про рендеринг, обработчики событий и microtasks. Лучше формулировать так: продолжение функции выполнится позже, когда Promise завершится. - 2
Забывать про ошибки
Rejected Promise уawaitведет себя как исключение в этой строке. Безtry/catchили обработки в вызывающем коде легко получить unhandled rejection, зависший loading и потерянное сообщение для пользователя. На интервью сразу скажите, где вы ловите ошибку и как возвращаете UI в нормальное состояние. - 3
Путать сетевую ошибку и HTTP-статус
await fetch(url)обычно не бросает ошибку на404или500. Если не проверитьresponse.ok, приложение может попытаться разобрать тело как успешные данные и показать неправильный экран. Безопаснее явно проверять статус передresponse.json(). - 4
Делать независимые запросы последовательными
Несколькоawaitподряд выглядят удобно, но могут увеличить время загрузки в два или три раза. Если запросы не зависят друг от друга, запускайте их одновременно и ждите черезPromise.all. Последовательность оставляйте только там, где второй шаг реально использует результат первого. - 5
Обновлять UI после устаревшего await
В React результатawaitможет прийти после смены пропсов, перехода на другой экран или размонтирования компонента. Старый запрос может перезаписать новые данные, оставить неверныйloadingили показать ошибку не для того экрана. ИспользуйтеAbortController, cleanup в эффекте или проверку актуального request id перед обновлением состояния. - 6
Думать, что async-функция возвращает обычное значение
Любаяasync-функция возвращаетPromise. Если вызвать ее безawaitили.then, вы получите не данные, а Promise. UI может отрисовать неправильное состояние. В ответе явно свяжитеasync,awaitи возвращаемый Promise.
Follow-up
Что могут спросить дальше
Короткие ответы на вопросы, которыми проверяют понимание await, Promise и обработки ошибок во фронтенде.
Живые ответы
Видео с похожим вопросом
Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.
Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.
Что такое асинхронность 😎
Асинхронность позволяет выполнять долгие операции так, чтобы основной поток не ждал их завершения. Разбираем, как это связано с event loop, промисами, async/await и типичными багами во фронтенде.
Что будет, если в Promise.all один Promise выдаст ошибку 😎
Promise.all отклонится с причиной первого отклоненного Promise, но не отменит остальные операции. Разберитесь, как ответить на интервью и когда выбрать Promise.all, Promise.allSettled или локальную обработку ошибок.