Как реализовать функцию retry для повторного вызова асинхронной функции при ошибке в JavaScript
Что проверяют
Цикл с await внутри try/catch, понимание, какие ошибки стоит повторять, и уверенность, что функция не зависнет и не устроит нагрузку на сервер. Следом обычно просят добавить экспоненциальную задержку, отмену через AbortSignal или фильтр по типу ошибки.
Как отвечать
Контракт: retry принимает фабрику, которая возвращает промис, а не сам промис. Промис уже запущен, повторить его нельзя, повторить можно только вызов функции. Параметры: число попыток, базовая задержка, shouldRetry и signal.
- цикл:
forсtry { return await fn(attempt); } catch (e) { ... }. Именноreturn await: безawaitотклонённый промис уйдёт наружу мимо этогоcatch; - задержка: экспоненциальная,
base * 2 ** attempt, с потолком и случайным разбросом (jitter). Без разброса все вкладки, получившие ошибку одновременно, одновременно же и придут повторно; - что повторять: сетевые сбои,
429,502,503,504. Ответы400,401,403,404от повтора не исправятся; Retry-After: при429и503сервер может сам сказать, сколько ждать, и это значение важнее вашей формулы;- отмена: проверять
signal.abortedмежду попытками и прерывать ожидание паузы. На каждую попытку удобно ставитьAbortSignal.timeout(ms); - итог: после последней попытки пробросить последнюю ошибку или
AggregateErrorсо всеми.
Упомяните идемпотентность: повтор POST может создать второй заказ, поэтому такие запросы повторяют только с ключом идемпотентности на стороне сервера. И скажите, что в TanStack Query повторы запросов уже включены по умолчанию, писать свой retry поверх не нужно.
Где спотыкаются
Передают в retry вызов fetchData() вместо самой функции. Первая ошибка прилетает, а повторять нечего: тот же отклонённый промис возвращается снова.
Забывают, что fetch отклоняется только при сетевом сбое. Ответ 500 приходит как успешный промис, поэтому без проверки response.ok и явного throw повтор не срабатывает никогда.
Путают попытки и повторы и не проверяют входные данные. При attempts = 0 наивный цикл не выполнится ни разу и тихо вернёт undefined вместо результата или ошибки.