Как избежать гонки запросов (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.