Как исправить проблему с логированием при потере интернет-соединения и повторных кликах
Что проверяют
Сможете ли вы сделать отправку событий надёжной: ничего не потерять при обрыве сети и ничего не задвоить, когда пользователь жмёт кнопку пять раз подряд. Следом обычно спрашивают, что будет с неотправленными логами, если вкладку закрыли.
Как отвечать
Разделите задачу на две: доставку и дедупликацию.
- очередь вместо прямой отправки: каждое событие сначала попадает в очередь, отдельный отправитель забирает его оттуда. Очередь хранится в IndexedDB или
localStorage, иначе перезагрузка страницы её обнулит; - идентификатор события: при создании присваиваете
crypto.randomUUID(). Повторная отправка после таймаута не создаст дубль, если сервер отбрасывает уже виденные id. Это и есть идемпотентность; - повторы с задержкой: при сетевой ошибке или ответе 5xx повторяете с экспоненциальной паузой и случайным разбросом, при 4xx не повторяете, это ошибка данных;
- пачки: отправляете не по одному событию, а массивом, так меньше запросов и проще сохранить порядок;
- закрытие вкладки: на
visibilitychangeв состоянииhiddenсбрасываете остаток черезnavigator.sendBeaconилиfetchсkeepalive: true; - повторные клики: отдельная проблема интерфейса. Кнопка блокируется на время запроса, а для действий вроде оформления заказа ключ идемпотентности живёт до получения ответа.
События online и offline полезны как подсказка, чтобы разбудить отправителя, но решение «отправлять или нет» принимает результат запроса.
Где спотыкаются
Доверяют navigator.onLine. Значение true означает только, что есть сетевой интерфейс: при Wi-Fi без интернета или недоступном сервере запрос всё равно упадёт.
Ставят флаг «уже отправляю» и выходят из функции. Если в этот момент пришло второе событие, оно не попадает в очередь и теряется, то есть защита от дублей превращается в потерю данных.
Не ограничивают очередь. За час без сети она разрастается, хранилище переполняется, а при восстановлении связи клиент одним залпом шлёт тысячи запросов. Нужны лимит размера и отправка пачками.