Интервью-вопрос
Что такое async/await в JavaScript
async/await это синтаксис поверх промисов, который помогает писать асинхронный код более линейно. На интервью важно не назвать его настоящей синхронностью. Покажите, как вы обрабатываете ошибки, параллельные запросы и состояние UI.
- Добавлен
- Редакция
Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.
Мини-квиз
Проверка перед разбором
Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.
Вопрос 1 из 50 правильно
Разбор
Разобраться, а не зазубрить
Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.
Базовая идея
async/await помогает писать асинхронный код как понятную последовательность действий. Он не заменяет промисы, а работает поверх них.
async меняет контракт функции. Такая функция всегда возвращает Promise. Если вы возвращаете строку, число или объект, вызывающий код все равно получит Promise. Этот Promise зарезолвится вашим значением.
await можно использовать внутри async-функции. Он ждет промис, thenable или обычное значение и возвращает успешный результат. Если промис отклонится, в месте await появится ошибка. Ее можно поймать через try/catch.
Короткий пример для ответа на интервью:
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`User request failed: ${response.status}`);
}
return response.json();
}
async function showUser() {
try {
const user = await getUser(42);
console.log(user.name);
} catch (error) {
console.error("Не удалось загрузить пользователя", error);
}
}Здесь getUser возвращает Promise с пользователем. await fetch ждет сетевой ответ. Проверка response.ok нужна отдельно, потому что HTTP-ошибка не всегда означает rejected Promise.
Что происходит при await
Удобная формулировка для интервью: await ставит паузу в текущей async-функции, но не замораживает весь JavaScript. Остальной код может продолжать выполняться. Браузер может обрабатывать события. Продолжение async-функции выполнится позже.
Это важно для понимания порядка выполнения:
async function run() {
console.log("1");
await Promise.resolve();
console.log("3");
}
run();
console.log("2");В консоли будет 1, потом 2, потом 3. Продолжение после await откладывается, даже если промис уже выполнен. Этот пример показывает, что вы понимаете связь async/await с event loop и микрозадачами.
Последовательно или параллельно
Частая практическая ловушка в том, что async/await делает код линейным, но не ускоряет его автоматически. Если написать несколько await подряд, операции будут ждать друг друга.
Плохой вариант для независимых запросов:
async function loadDashboard() {
const user = await fetchUser();
const settings = await fetchSettings();
const notifications = await fetchNotifications();
return { user, settings, notifications };
}Если эти запросы не зависят друг от друга, экран загрузится медленнее. Лучше запустить их сразу:
async function loadDashboard() {
const [user, settings, notifications] = await Promise.all([
fetchUser(),
fetchSettings(),
fetchNotifications(),
]);
return { user, settings, notifications };
}Но последовательный await не всегда ошибка. Он нужен, когда второй шаг использует результат первого. Например, сначала создать заказ, а потом оплатить именно этот заказ.
Как выбрать способ ожидания
Используйте последовательный await. Так проще контролировать порядок и ошибки.Запустите их сразу и дождитесь Promise.all. Так вы уменьшите общее время ожидания.Используйте Promise.allSettled. Потом покажите частичный результат и понятное состояние ошибки.Добавьте отмену через AbortController или флаг актуальности. Так вы не обновите размонтированный или устаревший экран.Ошибки и HTTP во фронтенде
В ответе разделите два типа ошибок. Rejected Promise из await можно поймать через try/catch. HTTP-статусы у fetch нужно проверять отдельно.
async function loadProducts(signal) {
const response = await fetch("/api/products", { signal });
if (!response.ok) {
throw new Error(`Products request failed: ${response.status}`);
}
return response.json();
}Если не проверить response.ok, можно распарсить тело ошибки как обычные данные или показать пользователю пустой список вместо понятного сообщения. На интервью это звучит сильнее, чем просто сказать, что вы оборачиваете запрос в try/catch.
- 1Поставить состояние loading
- 2Дождаться fetch через await
- 3Проверить response.ok
- 4Распарсить тело ответа
- 5В catch показать ошибку и не потерять старые данные
- 1Считать, что await fetch ловит 500
- 2Сразу вызвать response.json без проверки
- 3Не обработать отмену запроса
- 4Перезаписать UI пустым состоянием
- 5Оставить пользователя без понятной ошибки
Async/await в React и UI-коде
В компонентах важен не только синтаксис, но и жизненный цикл. Запрос может завершиться после размонтирования компонента. Еще он может завершиться после того, как пользователь уже выбрал другой фильтр. Тогда старый результат перезапишет новый.
Пример безопаснее делать с отменой:
import { useEffect, useState } from "react";
function UserCard({ userId }) {
const [state, setState] = useState({
status: "loading",
user: null,
error: null,
requestedId: userId,
});
useEffect(() => {
const controller = new AbortController();
async function loadUser() {
try {
setState({
status: "loading",
user: null,
error: null,
requestedId: userId,
});
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const nextUser = await response.json();
setState({
status: "success",
user: nextUser,
error: null,
requestedId: userId,
});
} catch (error) {
if (error.name !== "AbortError") {
setState({
status: "error",
user: null,
error,
requestedId: userId,
});
}
}
}
loadUser();
return () => controller.abort();
}, [userId]);
if (state.requestedId !== userId || state.status === "loading") {
return <p>Загрузка...</p>;
}
if (state.status === "error") {
return <p>Не удалось загрузить пользователя</p>;
}
if (!state.user) {
return <p>Пользователь не найден</p>;
}
return <p>{state.user.name}</p>;
}Этот пример показывает практический вывод. async/await помогает читать код, но не отменяет правила UI. Без статуса загрузки экран может показывать старого пользователя при смене userId. Без отмены старый запрос может перезаписать новый результат. Поэтому обрабатывайте загрузку, ошибку, пустое состояние, отмену и устаревший результат.
Как ответить на интервью
Хороший короткий ответ может звучать так:
async/await это синтаксис для работы с Promise. async-функция всегда возвращает Promise, а await внутри нее ждет результат промиса и позволяет писать код как последовательность шагов. При rejected Promise ошибка попадает в try/catch, но await не блокирует весь браузер. В практическом коде я слежу за тем, какие операции должны идти последовательно, какие можно запускать через Promise.all, и отдельно обрабатываю HTTP-статусы, отмену запроса и состояние UI.
Такой ответ закрывает базу и показывает практический опыт. Вы не просто знаете синтаксис. Вы понимаете, где async/await может создать медленный запрос, неотловленную ошибку или race condition.
Частые ошибки
Где обычно ошибаются
Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.
- 1
Считать async/await настоящей синхронностью
awaitне блокирует весь браузер. Он только переносит продолжение текущей async-функции в асинхронный поток выполнения. Если сформулировать иначе, на интервью легко получить уточняющий вопрос про event loop и порядок микрозадач. - 2
Забывать, что async возвращает Promise
Если вызвать async-функцию безawait, вы получите Promise, а не готовое значение. Во фронтенде это часто приводит к выводу[object Promise], неверному условию в JSX или неотловленной ошибке. Безопаснее явно ждать результат там, где он нужен. - 3
Писать await в цикле без причины
Последовательный цикл нужен только при зависимости шагов. Для независимых запросов он делает интерфейс медленнее, потому что каждый следующий запрос стартует после предыдущего. Лучше создать промисы заранее и дождаться их черезPromise.allилиPromise.allSettled. - 4
Ожидать, что fetch бросит ошибку на 500
fetchобычно не отклоняет Promise на HTTP-статусах вроде404или500. Если не проверитьresponse.ok, приложение может обработать ошибочный ответ как успешный. Корректный код отдельно проверяет статус и формирует понятное состояние ошибки. - 5
Не отменять устаревшие запросы
Если пользователь быстро меняет страницу, фильтр или id, старый запрос может завершиться позже нового и перезаписать актуальные данные. В компонентах стоит использоватьAbortController, проверку актуальности запроса или возможности data-fetching библиотеки. Это снижает риск race condition и мигающего UI.
Follow-up
Что могут спросить дальше
Короткие ответы на вопросы, которыми проверяют понимание async/await, промисов и ошибок во фронтенде.
Живые ответы
Видео с похожим вопросом
Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.
Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.
Что такое Event Loop? 😎
Event Loop связывает синхронный стек, очереди задач, микротаски и браузерные API. Вы разберете порядок выполнения, ловушки с промисами и таймерами, а также риск блокировки UI.
Что такое асинхронность 😎
Асинхронность позволяет выполнять долгие операции так, чтобы основной поток не ждал их завершения. Разбираем, как это связано с event loop, промисами, async/await и типичными багами во фронтенде.