Как обработать сетевую ошибку при отправке запроса, если неизвестно, был ли заказ оформлен?
Что проверяют
Понимаете ли вы, что сетевой сбой после отправки POST это неопределённость, а не ошибка: заказ мог создаться, а ответ потеряться по дороге. Следом спрашивают, как не создать дубль при повторе и что увидит пользователь, если закроет вкладку в этот момент.
Как отвечать
Начните с разделения исходов. fetch отклоняет промис только при сетевом сбое или отмене, на 4xx и 5xx он возвращает ответ, и response.ok нужно проверять самим. Дальше три группы: 4xx, кроме 408 и 429, значит, что сервер запрос отклонил и повтор без исправлений бессмыслен; таймаут, обрыв, 500, 502 и 504 означают «неизвестно»; 2xx значит «создан».
Для «неизвестно» решение строится на идемпотентности:
- ключ идемпотентности: при открытии оформления фронт генерирует
crypto.randomUUID()и шлёт его с каждой попыткой в заголовке, напримерIdempotency-Key. Сервер хранит ключ и результат, и повторный запрос возвращает уже созданный заказ вместо нового; - один ключ на намерение: ключ живёт, пока пользователь не изменил корзину, и лежит в
sessionStorage, чтобы пережить перезагрузку; - таймаут:
AbortSignal.timeout(15000)вfetch, иначе запрос может висеть минутами; - повтор: автоматически пару раз с экспоненциальной задержкой и случайным разбросом, строго с тем же ключом;
- сверка статуса: если повторы не помогли, запрос статуса по ключу или по черновику заказа, созданному заранее;
- интерфейс: кнопка блокируется на время запроса, а вместо «ошибка, попробуйте ещё раз» пользователь видит «проверяем, оформился ли заказ».
Добавьте, что без поддержки на бэкенде фронт эту задачу не решает, и контракт нужно согласовать с командой API.
Где спотыкаются
Трактуют 500 или 504 как «заказ не создан» и предлагают оформить заново. Именно так появляются двойные списания.
Генерируют новый ключ на каждую попытку. Тогда сервер видит разные запросы, и защита не работает.
Ограничиваются try/catch и alert. Это обработка исключения, а не ответ на вопрос, в каком состоянии заказ.