WebSocket даёт приложению канал обмена сообщениями. Но он не определяет, к какой операции относится ответ, сколько ждать и нужно ли выполнять повторный запрос ещё раз. Это решения на уровне приложения.
Эксперимент с запросами и ответами делает их видимыми. Всё работает в браузере: здесь нет настоящего сокета, бэкенда и измерения сетевой задержки. Цель — исследовать небольшой протокол в условиях, которые легко повторить.
Задача: ответ ещё не означает результат
Представьте две ожидающие операции. Вторая завершается первой. Если связывать очередной ответ с самым старым запросом, интерфейс обновит не ту операцию. Добавим тайм-аут: что делать с успешным ответом, который пришёл после сообщения об истечении времени ожидания?
Повторный клик или повторная попытка приложения могут отправить ту же логическую операцию ещё раз. Отбрасывание лишнего ответа защищает интерфейс, но не отменяет повторный эффект на сервере. Симуляция разделяет эти обязанности.
Три решения
Сопоставлять по ID. Каждая новая операция получает ID запроса. Дополнительные копии сохраняют его. Одна строка представляет одну логическую операцию независимо от числа доставок. Последовательные ID удобны для обучения; реальному протоколу нужны правила уникальности с учётом сессий и повторных попыток.
Завершать ожидание один раз. Операция начинается в ожидании и заканчивается успехом, ошибкой или тайм-аутом. Обработчик проверяет состояние перед изменением. Поздний или повторный ответ попадает в журнал и игнорируется. Своевременный ответ отменяет таймер ожидания. При равных настройках задержки и тайм-аута побеждает тайм-аут: его таймер регистрируется первым.
Основная проверка проста:
if (operation.status !== 'pending') { log(operation.id, 'ignored'); return;}Отдельно устранять повторное выполнение. Симуляция сервера выбирает один результат на ID. Копии возвращают его с интервалом 80 мс между ответами, чтобы их было видно. Клиент принимает только первый своевременный ответ. Модель показывает эффект дедупликации, но не реализует постоянное хранилище ключей идемпотентности.
Модель состояния отделена от интерфейса. Подставляемые часы позволяют точно продвигать время в проверках, а подставляемый источник случайных чисел — воспроизводить ошибки. В интерфейсе вероятность ошибки выбирается независимо для каждой логической операции. Значения 0 и 100 процентов дают повторяемые сценарии.
Проверяем границы
Начните со сценария Успешный ответ. Отправьте запрос, уменьшите задержку и отправьте следующий до завершения первого. Более поздний запрос может закончиться раньше, сохранив свой ID.
Затем выберите Поздний ответ. При задержке 2500 мс и тайм-ауте 1000 мс операция завершит ожидание до появления ответа в журнале. Позже статус не изменится. Тайм-аут прекращает ожидание клиента, но не доказывает, что реальный сервер ничего не выполнил.
В сценарии Дубликаты запросов две дополнительные копии дают три доставки, один результат сервера и один исход на клиенте. Изменение настроек влияет только на новые операции, поэтому разные условия можно сравнить в одном запуске.
Компромиссы и ограничения
Локальная симуляция жертвует реализмом ради управляемости. Она не проверяет сеть, разрывы соединения, переподключение, аутентификацию и управление потоком. «Ошибка» здесь означает ответ приложения с ошибкой, а не потерянный пакет. Таймеры браузера тоже не являются точными часами, особенно в фоновой вкладке.
Реальной системе нужны проверка сообщений, управление соединением и ограниченный реестр ожидающих запросов. Для повторной записи потребуется политика идемпотентности: область уникальности ключа, срок хранения результата, параллельные дубликаты и атомарность записи ключа вместе с эффектом. Новый ID при каждой повторной попытке обесценит дедупликацию по ID.
Эксперимент ограничен 40 операциями и 100 последними событиями. Очистка запуска или уход со страницы отменяет таймеры. Это границы учебной сессии, а не тест пропускной способности.
Выводы
Доставка, выполнение и результат для пользователя — разные события. Запрос может быть доставлен несколько раз, выполнен один раз и всё равно завершиться тайм-аутом на клиенте. Строки операций и журнал помогают увидеть эту разницу.
Начните с простых инвариантов: идентифицируйте операцию, определите конец ожидания и назначьте ответственность за дубликаты. Проверьте пограничные случаи до добавления переподключений и повторных попыток.
Вернитесь в Lab и измените условия. Настоящий браузерный API, отправку, получение и очистку соединения описывает руководство MDN по WebSocket.
Комментарии