Как реализовать таймаут с abort для запроса в JavaScript
Что проверяют
Знаете ли вы, что у fetch нет своего таймаута, и умеете ли отменять запрос, а не просто перестать ждать результат. Следом часто спрашивают, как совместить таймаут с отменой по действию пользователя, например при уходе со страницы.
Как отвечать
Базовый вариант строится на AbortController: создаёте контроллер, передаёте controller.signal в fetch, через setTimeout вызываете controller.abort(). Таймер снимаете в finally, чтобы он не висел и после ошибки.
Дальше покажите современный способ, он короче:
AbortSignal.timeout(ms): готовый сигнал, который сработает сам. Запрос отклоняется сDOMExceptionпо имениTimeoutError, а неAbortError;AbortSignal.any([...]): объединяет сигналы, например таймаут и сигнал компонента. Сработает любой;- различение причин:
TimeoutErrorэто таймаут,AbortErrorэто ручная отмена, её обычно не показывают как ошибку, аTypeErrorэто сетевой сбой; response.ok:fetchне отклоняет промис на 404 и 500, статус проверяют руками.
Важная деталь: сигнал действует и на чтение тела. Если таймаут живёт до конца await response.json(), зависшая передача данных тоже будет прервана. В React отмену вешают на функцию очистки useEffect, чтобы ответ старого запроса не попал в состояние после размонтирования. В axios то же делается опцией signal, у него есть и свой timeout.
Где спотыкаются
Снимают таймер, как только пришёл response. Сервер ответил статусом, но тело тянется минуту, и таймаут на это уже не распространяется.
Считают, что отмена останавливает работу на сервере. Браузер лишь разрывает соединение, сервер может довести операцию до конца, поэтому повтор неидемпотентного POST после таймаута способен создать дубль.
Делают таймаут через Promise.race с таймером без AbortController. Промис «проиграл», но запрос живёт дальше, держит соединение и может позже перезаписать данные.