В 18:00 в Москве сервис цены Яндекс Такси перегружен, и часть запросов получает 503. Если все клиенты повторят запрос через 200, 400, 800 мс, повторы придут волнами, ровно в одни и те же моменты. Поэтому паузу делают случайной: full jitter.
Напишите retryWithJitter(fn, { retries = 4, base = 200, cap = 5000, random = Math.random } = {}). fn() возвращает промис. После n-й неудачи (n с нуля) ждём Math.floor(random() * Math.min(cap, base * 2 ** n)) мс и вызываем снова. Всего попыток — retries + 1. Пауза отсчитывается от момента ответа.
Если у ошибки есть retryAfter (мс, из заголовка Retry-After), ждём ровно столько, а random не вызываем. Ошибки со status от 400 до 499, кроме 429, не повторяем: неверный адрес не станет верным. После последней неудачи отклоняем последней ошибкой.
В работе: Повтор запросов к перегруженному API, переподключение WebSocket, фоновые синхронизации.
Пример 1: успех с первого раза
["ok"], [0.5], null
{"out": {"ok": "Эконом: 412 ₽"}, "calls": [0], "randomCalls": 0}Пример 2: один 503
["503", "ok"], [0.5], null
{"out": {"ok": "Эконом: 412 ₽"}, "calls": [0, 150], "randomCalls": 1}retryWithJitter — тест вызывает её сам. Таймеры и Date в тестах ненастоящие: время идёт по команде теста, поэтому setTimeout на 5 секунд проверяется мгновенно. Тестов 10: 2 открыты выше, остальные приходят после старта подхода. Лимит — 2 с на тест.
function retryWithJitter(fn, { retries, base, cap, random } = {}) // → Promise
Источник: на задачах Яндекс Такси: расчёт цены поездки. Условие пересказано своими словами.
Ожидаемый ответ не нужен: покажем, что вернула функция.
Здесь появится результат отправки.