PassFront
  1. PassFront
  2. База знаний
  3. Асинхронность и Event Loop

Как избежать гонки запросов (race condition) при повторных асинхронных запросах?

Асинхронность и Event Loop

Гонка запросов возникает, когда ответ на более старый запрос приходит позже нового и затирает актуальные данные: пользователь сменил фильтр, а на экране результат прошлого.

Пример проблемы

async function search(query) {
  const res = await fetch(`/api?q=${query}`);
  setResults(await res.json()); // может прийти ответ на устаревший query
}

Отмена через AbortController

let controller;

async function search(query) {
  controller?.abort();
  controller = new AbortController();
  try {
    const res = await fetch(`/api?q=${query}`, { signal: controller.signal });
    setResults(await res.json());
  } catch (e) {
    if (e.name !== 'AbortError') throw e;
  }
}

Предыдущий запрос отменяется, его ответ не будет обработан.

Игнорирование устаревшего ответа

Если отменить нельзя, запоминают номер запроса и применяют только последний:

let current = 0;
async function search(query) {
  const id = ++current;
  const data = await load(query);
  if (id === current) setResults(data);
}

В React

В useEffect отмену выполняют в функции очистки:

useEffect(() => {
  const controller = new AbortController();
  fetch(url, { signal: controller.signal }).then(r => r.json()).then(setData).catch(() => {});
  return () => controller.abort();
}, [url]);

Готовые решения (React Query, SWR) делают это сами.

Коротко для собеседования

Гонка — это применение устаревшего ответа. Лечится отменой предыдущего запроса через AbortController либо проверкой «это ещё последний запрос?» перед записью результата; в React — через cleanup в useEffect.

Другие вопросы раздела