Интервью-вопрос
Что такое асинхронность
Асинхронность позволяет не блокировать основной поток ожиданием долгих операций. В ответе важно не перепутать ее с параллелизмом и показать, как вы избегаете ошибок, гонок и зависаний UI.
- Добавлен
- Редакция
Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.
Мини-квиз
Проверка перед разбором
Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.
Вопрос 1 из 50 правильно
Разбор
Разобраться, а не зазубрить
Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.
Базовая идея
Асинхронность отвечает на простой вопрос: что делать, если операция завершится не сразу. Например, запрос к серверу может занять 50 мс, а может 5 секунд. Если бы JavaScript ждал его синхронно в основном потоке, страница не реагировала бы на клики, ввод и прокрутку.
Поэтому долгую операцию запускают, а продолжение откладывают до момента, когда появится результат. JavaScript не ждет внутри call stack. Он может выполнить другой код, обработать событие пользователя и позже вернуться к callback, .then() или продолжению после await.
Хороший короткий ответ может звучать так:
Асинхронность позволяет выполнять операции, результат которых появится позже, не блокируя основной поток ожиданием. В JavaScript это обычно callbacks, Promise и async/await. При этом асинхронность не равна параллельному выполнению всего кода, поэтому для UI важны обработка ошибок, отмена и порядок ответов.
Как это работает в JavaScript
В JavaScript есть call stack. В нем выполняется текущий синхронный код. Когда вы вызываете асинхронное API, например fetch, таймер или обработчик события, продолжение не выполняется сразу. Оно будет поставлено в очередь и вернется в выполнение через event loop.
Классический пример показывает порядок:
console.log("start");
setTimeout(() => {
console.log("timeout");
}, 0);
Promise.resolve().then(() => {
console.log("promise");
});
console.log("end");Результат будет таким:
start
end
promise
timeoutСначала выполняется весь синхронный код. Потом браузер разбирает microtasks, куда попадают продолжения промисов. После этого доходит очередь до macrotasks, например таймера. Для интервью обычно достаточно объяснить это на уровне очередей, без глубокой лекции про спецификацию.
Чем асинхронность не является
Самая частая ловушка: сказать, что асинхронность это параллельное выполнение. Такое объяснение звучит уверенно, но технически оно неточно. Асинхронность позволяет не блокировать выполнение ожиданием, но синхронный тяжелый код все равно займет основной поток.
Плохой пример:
Promise.resolve().then(() => {
let sum = 0;
for (let i = 0; i < 5_000_000_000; i += 1) {
sum += i;
}
console.log(sum);
});Этот код асинхронно ставит вычисление в microtask, но само вычисление потом выполняется в основном потоке. UI может зависнуть. Безопасный вывод: для тяжелых вычислений нужен Web Worker, сервер, оптимизация алгоритма или дробление работы на части.
Как выбирать подход в коде
На интервью полезно не просто назвать callbacks, Promise и async/await. Лучше показать критерий выбора. Сейчас в обычном frontend-коде чаще всего используют промисы и async/await, потому что они проще читаются и хорошо сочетаются с обработкой ошибок.
Как выбрать асинхронный подход
Используйте async/await и обрабатывайте ошибку рядом с местом ожидания.Запускайте их вместе и ждите Promise.all или Promise.allSettled.Добавьте отмену через AbortController или проверку актуальности ответа.Выносите работу в Web Worker или разбивайте ее на небольшие шаги.Практический пример во фронтенде
В реальном UI асинхронность почти всегда связана с состоянием. Нужно показать loader, дождаться данных, обработать ошибку, не применить устаревший ответ и не оставить пользователя с пустым экраном.
Опасный вариант выглядит коротко, но ломается при быстрой смене фильтра:
useEffect(() => {
setLoading(true);
fetch(`/api/users?q=${query}`)
.then((response) => response.json())
.then(setUsers)
.finally(() => setLoading(false));
}, [query]);Что здесь пойдет не так: старый ответ может перезаписать новые данные, ошибка сети оставит UI без понятного состояния, а строка запроса не кодируется. Если компонент размонтируется, запрос продолжит жить без пользы. Безопаснее управлять жизненным циклом запроса явно.
import { useEffect, useState } from "react";
function UsersList({ query }) {
const [users, setUsers] = useState([]);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const controller = new AbortController();
async function loadUsers() {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users?q=${encodeURIComponent(query)}`, {
signal: controller.signal,
});
if (!response.ok) {
throw new Error("Failed to load users");
}
const data = await response.json();
if (!Array.isArray(data)) {
throw new Error("Unexpected response format");
}
setUsers(data);
} catch (error) {
if (error.name !== "AbortError") {
setError(error);
}
} finally {
if (!controller.signal.aborted) {
setLoading(false);
}
}
}
loadUsers();
return () => {
controller.abort();
};
}, [query]);
if (loading) return <p role="status">Loading...</p>;
if (error) return <p role="alert">Failed to load users</p>;
if (users.length === 0) return <p>No users found</p>;
return <Users items={users} />;
}Здесь важен не сам fetch, а жизненный цикл. Если query изменился или компонент размонтировался, старый запрос отменяется. Проверка формы ответа защищает UI от неожиданного API-ответа. role="status", role="alert" и empty state помогают не потерять состояние для пользователя и скринридера.
- 1Показать состояние загрузки.
- 2Запустить запрос с возможностью отмены.
- 3Проверить успешность ответа.
- 4Обновить только актуальное состояние.
- 5Обработать ошибку и отмену отдельно.
- 1Запустить запрос и забыть про него.
- 2Не ловить reject и сетевые ошибки.
- 3Показать ответ, даже если пользователь уже выбрал другое.
- 4Обновить состояние после размонтирования.
- 5Спрятать проблему за общим сообщением об ошибке.
Практический вывод
В ответе на интервью держите простой порядок: определение, механизм, практический риск. Сначала скажите, что асинхронность позволяет не блокировать основной поток ожиданием. Затем назовите Promise и async/await. Потом добавьте, что во frontend-коде нужно контролировать ошибки, отмену и порядок результатов.
Если хотите усилить ответ, приведите короткий пример. Несколько независимых запросов можно запускать параллельно с точки зрения ожидания через Promise.all, но тяжелое вычисление лучше вынести в Web Worker. Это покажет, что вы не просто повторяете определение, а понимаете ограничения модели.
Частые ошибки
Где обычно ошибаются
Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.
- 1
Путать асинхронность с параллелизмом
Асинхронный код не означает, что весь JavaScript сразу работает в нескольких потоках. Если ответить так, вас легко проверят вопросом про тяжелый цикл, который блокирует UI. Надежнее сказать, что асинхронность освобождает call stack от ожидания, но CPU-heavy код все равно нужно выносить или дробить. - 2
Считать await блокировкой браузера
awaitприостанавливает выполнение текущейasync-функции, но не блокирует весь основной поток. Пользователь может продолжать взаимодействовать со страницей, если сам код не занят синхронной тяжелой работой. На интервью лучше явно разделять паузу внутри функции и блокировку UI. - 3
Не обрабатывать ошибки промисов
Необработанныйrejectлегко превращается в плохой UX: бесконечный loader, пустой экран или потерянную причину ошибки. Добавляйтеtry/catch,.catch()или обработку состояния ошибки в библиотеке данных. Так вы показываете, что думаете не только о happy path. - 4
Запускать независимые запросы строго по очереди
Последовательныйawaitудобен, но он может лишний раз растянуть загрузку страницы. Если запросы не зависят друг от друга, их лучше запустить одновременно и дождаться общего результата. Если один запрос зависит от другого, последовательность оправдана. - 5
Игнорировать устаревшие ответы
Во фронтенде пользователь может быстро сменить фильтр, страницу или вкладку, а старый запрос вернется позже нового. Без проверки актуальности вы покажете неправильные данные. ИспользуйтеAbortController, request id или инструмент, который умеет отмену и дедупликацию.
Follow-up
Что могут спросить дальше
Короткие ответы на вопросы, которыми проверяют понимание асинхронности, event loop и безопасной работы с UI.
Живые ответы
Видео с похожим вопросом
Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.
Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.
Что такое async/await в JavaScript 😎
async/await это синтаксис для работы с промисами, который делает асинхронный код проще читать. На странице разбираем, что возвращает async-функция, как работает await, где нужны try/catch и когда запросы лучше запускать параллельно.
Что такое await 😎
await приостанавливает выполнение async-функции до завершения Promise и возвращает его результат или выбрасывает ошибку. Разбираем, что именно он блокирует, как обрабатывать ошибки и когда запускать операции параллельно.