Отключение кнопки сокращает двойные клики, но не защищает от повтора после перезагрузки или сетевого таймаута. Надёжное подавление дублей требует контракта принимающей системы.
Защита интерфейса
export function singleFlight(send) {
let busy = false;
return async (...args) => {
if (busy) return;
busy = true;
try { return await send(...args); }
finally { busy = false; }
};
}
Функция не запускает второй одновременный send. После завершения она позволяет новую попытку. Это защита одной страницы, а не гарантия одной записи в базе.
Что должен решить сервер
Если endpoint поддерживает идемпотентность, один пользовательский запрос получает стабильный ключ. При повторе того же содержимого используется тот же ключ. Новое действие требует нового ключа. Формат заголовка, срок хранения и ответ на конфликт задаёт сервер.
Не добавляйте случайный Idempotency-Key на каждую сетевую попытку и не считайте задачу решённой. Неизвестный сервер может вообще проигнорировать заголовок. Используйте ключ идемпотентности только по контракту принимающей стороны.
Негативные сценарии
Одновременно вызовите функцию дважды: send должен выполниться один раз. Затем дождитесь завершения и проверьте новый вызов. На своём endpoint отдельно воспроизведите таймаут после сохранения и повтор того же ключа; проверьте сохранённые данные, а не только ответ UI.
Источники и дата проверки
Редакционная сверка: 2026-10-27. Учитывайте версию инструментов в своём проекте.
Обновлено