Как бороться с race condition при медленных ответах от бэкенда в поиске
Что проверяют
Понимаете ли вы, что ответы приходят не в том порядке, в каком ушли запросы, и что показать нужно результат последнего запроса, а не последнего пришедшего ответа. Следом спрашивают, как это сделать в React и что происходит с отменённым запросом на сервере.
Как отвечать
Сначала опишите сценарий: пользователь набрал «re», потом «react». Запрос по «re» тяжелее и отвечает через две секунды, по «react» через полсекунды. Без защиты старый ответ приходит последним и перетирает свежий: в поле «react», в списке результаты по «re».
Способы, от основных к вспомогательным:
- отмена через
AbortController: на каждый новый ввод отменяете предыдущий запрос, вfetchпередаётсяsignal. Отменённый промис отклоняется сAbortError, его ловят и молча игнорируют, а не показывают как ошибку; - игнорирование устаревших ответов: запоминаете id последнего запроса и применяете ответ, только если он совпадает. В
useEffectэто флагignore, который выставляется в cleanup, так советует документация React; - готовые инструменты: TanStack Query хранит данные по ключу
['search', query], поэтому ответ на старый ключ не попадёт под новый, а вqueryFnпередаётsignalдля отмены. В RxJS то же даётswitchMap; - debounce около 300 мс: уменьшает число запросов и нагрузку, но гонку не устраняет.
Уточните про сервер: отмена на клиенте закрывает соединение, но бэкенд может доработать запрос до конца. Если поиск тяжёлый, серверу стоит самому отслеживать разрыв соединения и прекращать работу.
Для UX: индикатор загрузки, рядом с результатами запрос, по которому они получены, кэш недавних ответов.
Где спотыкаются
Считают дебаунс решением. Он только снижает вероятность: при медленной сети два запроса всё равно уходят с интервалом больше задержки, и медленный обгоняет быстрый.
Предлагают выстроить запросы в очередь и выполнять по одному через await. Порядок будет правильный, но пользователь ждёт сумму всех задержек.
Отменяют запрос, но не обрабатывают AbortError, и на каждый быстрый ввод пользователь видит всплывающую ошибку.