DarkRiDDeR14 мин

Плохая сеть: диагностировать неизвестный результат и не потерять черновик

FrontendТестирование

После жалобы «сохранение иногда пропадает» легко начать с увеличения timeout. Это опасная реакция: проблема может быть в раннем success toast, в очистке draft до acknowledgement, в повторной отправке или в позднем ответе, который относится к уже неактуальной попытке. Если сразу менять transport, команда не узнает, какой факт видит пользователь. Цена ошибки — потерянный текст и повторяющийся дефект, который нельзя надёжно воспроизвести из-за отсутствия состояния.

Для диагностики я бы взял один учебный сценарий и запретил ему изображать реальную сеть. В fixture есть declared offline, затем unknown-outcome, затем один retry и payload acknowledgement. Нет fetch, network throttling, browser trace, таймеров и сервера. Эта узкая граница полезна: можно сначала доказать, что UI выдерживает stale reply и сохраняет новый draft, а уже потом собирать реальный стенд с версиями и условиями.

Сначала назовите наблюдение, а не диагноз

Матрица первичной диагностики
Наблюдение на экранеНельзя заключатьЧто проверить в stateБезопасное первое действие
Пользователь не увидел результатаоперация точно не выполненаphase, request key, attempt key, acknowledgementпоказать unknown outcome и оставить draft
Успех показан сразу после кликасервер подтвердил версиюкто выставляет success state и от какого payloadперенести success после validation acknowledgement
После retry приходит старый ответэто подтверждение текущей попыткисовпадает ли attempt keyпроигнорировать stale reply без очистки draft
Открылся пустой редакторсохранение прошлоpersisted draft и его version перед очисткойвосстановить локальную копию или честно показать её отсутствие
Пользователь набрал новый текстстарый ack можно применить ко всему экрануaccepted version и current draft versionподтвердить старую операцию, но сохранить новый текст

Один разбор: поздний ответ после повторной попытки

В модели пользователь создаёт draft версии 1, запускает attempt-01 и не получает model acknowledgement. Экран показывает результат неизвестен. Offline label блокирует retry, но draft остаётся в persisted copy. После declared online создаётся attempt-02 с тем же requestKey. Затем прилетает payload для первой попытки. Он выглядит правдоподобно: та же версия и тот же request key. Но attempt key другой, значит это не разрешение текущего ожидания. Модель возвращает stale-or-invalid-acknowledgement и не меняет visible state.

Это правило защищает не от «плохого ответа», а от неправильного владельца перехода. Старый payload может быть полезен для журнала или отдельной сверки, но он не должен закрывать форму автоматически. Сначала UI обязан решить, что он подтверждает. В учебном контракте ответ подтверждает только конкретную ожидаемую попытку. Реальный продукт может выбрать более сложную сверку через серверный статус, но тогда и это действие нужно описать отдельно, а не спрятать в catch-block.

Fixture: поздний и duplicate payload не делают второй commit

Ниже показан только вызов принятия acknowledgement. До него fixture уже проверила ключи и фазу. Payload создан вручную; поле submissionId — не ID реальной базы. Сначала ответ от attempt-01 отвергается как stale. Затем acknowledgement для attempt-02 принимает версию 1. Если после этого пришёл ещё один такой же payload, результат — duplicate-acknowledgement, а второй commit не происходит.

const payload = {
  attemptKey: "attempt-02",
  requestKey: "draft-v1",
  acceptedVersion: 1,
  submissionId: "submission-42",
};

acceptTeachingAcknowledgement(model, payload);
// stale reply с attempt-01 будет проигнорирован.
// Если появился draft v2, acknowledgement v1 не очищает v2.

В сценарии есть ещё одна неприятная деталь. Между retry и правильным acknowledgement пользователь меняет текст. Draft становится версией 2. Accepted payload относится к версии 1, поэтому модель выбирает acknowledged-newer-draft-retained. Она не скрывает прежний успех, но не уничтожает новую работу. Такой ответ интерфейса длиннее одного toast, зато он объясняет ситуацию: «предыдущее сохранение подтверждено; текущая правка ещё черновик».

Диагностическая схема: отсутствие учебного acknowledgement оставляет draft и показывает unknown outcome; offline retry блокируется; online retry получает новый attempt key; reply старой попытки игнорируется; ack версии 1 не очищает новый draft версии 2; отдельная recovery-ветка сохраняет draft для ручной проверки.
Схема не изображает HTTP-пакеты. Она помогает выбрать действие для каждого наблюдаемого состояния, прежде чем менять сетевой слой.

Восстановление должно быть безопаснее догадки

Когда outcome неизвестен, интерфейс часто предлагает слишком смелую кнопку: «Отправить ещё раз». Она может быть полезна, но не должна быть единственным путем. В модели keepDraftForRecovery переводит фазу в recovery-required, оставляет persisted draft и не планирует transport. Это не откат сервера и не восстановление связи. Это честный переход: пользовательский текст сохранён, а предметный результат требует отдельной проверки.

Такой путь особенно важен для операций, где повтор имеет цену. Для комментария можно предусмотреть согласованный retry; для изменения реквизитов или финансового действия может потребоваться сверка со списком операций, обращение в поддержку или серверный status endpoint. Текст статьи не назначает политику за продукт. Он требует, чтобы UI не стирал единственный артефакт, который точно находится под его контролем: локально сохранённый черновик.

Как собрать реальную проверку после модели

После unit fixture нужна другая работа, и её нельзя выдать за уже сделанную. Зафиксируйте browser, версию приложения, способ включения offline или потери ответа, тип формы, содержимое тестового черновика и ожидаемые видимые states. Запишите, где лежит реальное persistence, как очищается test data и каким способом сервер выдаёт acknowledgement. Затем отдельно проведите сценарии: отправка без ответа, retry, stale reply, редактирование до позднего ответа и восстановление после перезагрузки.

Результатом должен стать trace или тестовый отчёт с условиями, а не просто фраза «проверено на плохой сети». W3C Service Workers CRD напоминает, что worker — не вечный фон. Fetch Review Draft описывает слой platform fetching, но не бизнес-подтверждение. Эти источники полезны, чтобы не путать уровни, однако не заменяют наблюдение выбранного браузера и API конкретного продукта.

Маршрут: симптом → причина → проверка → действие

  1. Симптом. Зафиксируйте один факт: success без acknowledgement, пустой draft, старый reply после retry или новая версия, исчезнувшая после save. Не называйте это «сетью» до проверки state.
  2. Причина. Проверьте, не объединены ли draft, persisted copy, request key, attempt key и acknowledgement одним boolean или одним reducer case.
  3. Проверка модели. Выполните node web/scripts/upgrade-2022-07.mjs --verify-fixture. PASS означает только соблюдение учебного контракта: offline block, unknown outcome, retry guard, stale/duplicate reply и retention нового draft.
  4. Проверка реализации. В реальном коде найдите все места, которые очищают draft или показывают success. Для каждого запишите, каким acknowledgement и какой version он разрешён.
  5. Действие. Добавьте named visible states и один безопасный путь recovery. Запретите очистку persisted draft, пока accepted version не совпала с его версией.
  6. Проверка после изменения. Проведите контролируемый browser-сценарий с документированными условиями. Если он расходится с моделью, исправьте contract или зафиксируйте платформенное ограничение, а не маскируйте его повтором.

Что не исправляет эта статья

Не исправит проблему один глобальный indicator online/offline. Он может быть полезным сигналом, но не сообщает, приняла ли предметная операция версию текста. Не исправит проблему и бесконечный retry: он может ухудшить последствия и скрыть факт неизвестного исхода. Не исправит её один service worker: без contract для keys, persistence и acknowledgement фоновые задачи делают состояние менее, а не более понятным.

Модель также не покрывает несколько вкладок, параллельные формы, undo после подтверждения, безопасность локального текста, права пользователя, обновление приложения и различие между DNS, captive portal и серверной ошибкой. Эти темы нельзя свести к одному boolean network state. Хорошая новость в том, что базовое разделение draft / attempt / acknowledgement остаётся полезным и перед следующей сложностью: оно даёт место, куда добавить новый факт, не ломая существующие переходы.

Ограничение и следующий шаг

Учебная модель не является network test и не даёт production SLO. Её ключи, retry limit, тексты и payload придуманы для детерминированной проверки. Если ваш API не умеет признать logical request или не возвращает версию, нельзя заявлять, что простая client-side защита решила дубли. Нужно сначала договориться о server contract и только затем выбирать transport и UI.

Следующий практический шаг — добавить в одну форму журнал переходов для development или теста: draft version, request key, attempt key, phase, source acknowledgement и причина очистки. Не храните в нём чувствительный текст. Затем попросите reviewer пройти матрицу этой статьи на одном PR. Если он может показать, почему старый payload не меняет новый draft и где остаётся текст после unknown outcome, у изменения уже есть проверяемая граница.

Историческая граница июля 2022

Пакет опирается на Fetch Review Draft 2021 года, Service Workers Candidate Recommendation Draft от 12 июля 2022 года и RFC 9110 июня 2022 года. Все источники имеют датированные, неизменяемые URL. Они используются для терминов и границ платформы; fixture не утверждает, что в ней был реальный offline, HTTP-запрос, service worker или server acknowledgement.

Проверяемые источники

  • WHATWG Fetch Standard, Review Draft от 19 декабря 2021 года — датированный первичный снимок Fetch Standard. Он задаёт модель request, response и network error; он не превращает отсутствие application acknowledgement в доказательство, что серверное действие не произошло.
  • W3C Service Workers, Candidate Recommendation Draft от 12 июля 2022 года — неизменяемая версия периода: service worker событийный и может быть остановлен user agent. В этой партии service worker намеренно не создаётся; документ используется только как историческая граница платформы.
  • RFC 9110: HTTP Semantics, июнь 2022 года — нормативный RFC, доступный к июлю 2022 года. Он различает свойства HTTP-методов и не заменяет прикладной ключ запроса или подтверждение предметной операции.