Gernar
Frontend DeveloperJavaScript, асинхронность

Интервью-вопрос

Что такое async/await в JavaScript

async/await это синтаксис поверх промисов, который помогает писать асинхронный код более линейно. На интервью важно не назвать его настоящей синхронностью. Покажите, как вы обрабатываете ошибки, параллельные запросы и состояние UI.

Добавлен
Редакция

Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.

🐰0
🥚0

Мини-квиз

Проверка перед разбором

Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.

Вопрос 1 из 50 правильно

Что ответить, если вас спрашивают про возвращаемое значение async-функции?

Вы объясняете базовое поведение async перед примером с загрузкой данных.

Варианты ответа

Разбор

Разобраться, а не зазубрить

Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.

Базовая идея

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 не всегда ошибка. Он нужен, когда второй шаг использует результат первого. Например, сначала создать заказ, а потом оплатить именно этот заказ.

Как выбрать способ ожидания

1Следующий запрос зависит от результата предыдущего?
Используйте последовательный await. Так проще контролировать порядок и ошибки.
2Запросы независимы и нужен весь набор данных?
Запустите их сразу и дождитесь Promise.all. Так вы уменьшите общее время ожидания.
3Часть запросов может упасть, но остальные данные полезны?
Используйте Promise.allSettled. Потом покажите частичный результат и понятное состояние ошибки.
4Запрос связан с жизненным циклом компонента?
Добавьте отмену через 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. 1Поставить состояние loading
  2. 2Дождаться fetch через await
  3. 3Проверить response.ok
  4. 4Распарсить тело ответа
  5. 5В catch показать ошибку и не потерять старые данные
Рискованно
  1. 1Считать, что await fetch ловит 500
  2. 2Сразу вызвать response.json без проверки
  3. 3Не обработать отмену запроса
  4. 4Перезаписать UI пустым состоянием
  5. 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. 1

    Считать async/await настоящей синхронностью

    await не блокирует весь браузер. Он только переносит продолжение текущей async-функции в асинхронный поток выполнения. Если сформулировать иначе, на интервью легко получить уточняющий вопрос про event loop и порядок микрозадач.
  2. 2

    Забывать, что async возвращает Promise

    Если вызвать async-функцию без await, вы получите Promise, а не готовое значение. Во фронтенде это часто приводит к выводу [object Promise], неверному условию в JSX или неотловленной ошибке. Безопаснее явно ждать результат там, где он нужен.
  3. 3

    Писать await в цикле без причины

    Последовательный цикл нужен только при зависимости шагов. Для независимых запросов он делает интерфейс медленнее, потому что каждый следующий запрос стартует после предыдущего. Лучше создать промисы заранее и дождаться их через Promise.all или Promise.allSettled.
  4. 4

    Ожидать, что fetch бросит ошибку на 500

    fetch обычно не отклоняет Promise на HTTP-статусах вроде 404 или 500. Если не проверить response.ok, приложение может обработать ошибочный ответ как успешный. Корректный код отдельно проверяет статус и формирует понятное состояние ошибки.
  5. 5

    Не отменять устаревшие запросы

    Если пользователь быстро меняет страницу, фильтр или id, старый запрос может завершиться позже нового и перезаписать актуальные данные. В компонентах стоит использовать AbortController, проверку актуальности запроса или возможности data-fetching библиотеки. Это снижает риск race condition и мигающего UI.

Follow-up

Что могут спросить дальше

Короткие ответы на вопросы, которыми проверяют понимание async/await, промисов и ошибок во фронтенде.

Живые ответы

Видео с похожим вопросом

Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.

Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.

Содержание