Перейти к содержанию

Повторы, отключение сети и прогресс

«Блокнот» уже умеет отправлять, получать и разрешать конфликты. Теперь требуется порядок запусков: сеть может пропадать, пользователь нажимает кнопку несколько раз, а сервер временно не отвечает. Результат главы — ограниченный проход с повтором через увеличивающийся интервал, который сохраняет outbox и не объявляет доступность по одному браузерному событию online.

Планировщик находится в public/sync.js конечного архива. Автор не запускал приложение, не отключал реальную сеть и не измерял интервалы доставки. Числа таймеров ниже являются параметрами подготовленного кода, а не наблюдениями. Смысл online сверён с MDN: это сигнал о состоянии сети браузера, который сам по себе не подтверждает доступность нашего сервера.

Один проход имеет конечную работу

Run проверяет stopped и busy. Если синхронизация уже идёт в этой вкладке, новое нажатие не создаёт параллельный проход. Перед работой очищается прежний timer, а busy устанавливается до ожидания блокировки. В finally busy снимается даже после ошибки. Такой порядок предотвращает накопление независимых таймеров после каждого сохранения заметки.

Внутри pass отправляются не больше пятидесяти операций, затем читается одна страница pull. Ограничение защищает интерфейс от бесконечной работы под блокировкой, если очереди постоянно пополняются. После страницы функция сообщает, есть ли hasMore либо queued операции. Для продолжения назначается короткий интервал, при обычном завершении — более редкий следующий опрос.

Такой проход не является транзакцией всей синхронизации. Ранее принятые подтверждения и страницы остаются сохранёнными, если следующая отправка получила сетевую ошибку. Не требуется откатывать весь аккаунт к началу прохода. Каждый прикладной шаг уже имеет собственную атомарность и ID; планировщик только выбирает, когда продолжить обработку оставшегося состояния.

Временная ошибка оставляет намерение

Если fetch завершается timeout или неподходящим HTTP статусом, клиент не удаляет операцию. Он показывает сообщение и назначает повтор. Величина attempt увеличивается после неуспешного прохода и обнуляется после успешной страницы. При закрытии вкладки сам счётчик может потеряться, но outbox сохраняется; следующий запуск начинает новую попытку с тем же payload.

Следующий фрагмент отражает выражение из finally. Он вычисляет будущую задержку, а не запускает отдельную команду измерения.

const delay = failed
  ? Math.min(60000, 1000 * 2 ** Math.min(attempt, 6)) + Math.random() * 500
  : again ? 100 : 10000;
if (!stopped) timer = setTimeout(run, delay);

Экспоненциальное увеличение снижает частоту запросов при устойчивом отказе. Верхняя граница не позволяет ожиданию расти бесконечно. Небольшая случайная добавка уменьшает совпадение повторов нескольких копий, запущенных одновременно. Эти параметры пригодны как учебная отправная точка; их нельзя называть оптимальными для любого продукта без фактического анализа нагрузки и требований к задержке.

Повтор неизменяемой операции безопасен только благодаря server ledger. Таймер сам не обеспечивает идемпотентность. Если он создаёт новый opId при каждом вызове, увеличенная задержка лишь медленнее размножает отдельные намерения. Поэтому порядок работы начинается с сохранённого outbox, а параметры retry не меняют ни baseRevision, ни body существующего снимка.

Сессия и конфликт имеют другой исход

Ответ 401 переводит stopped в true. Программа не продолжает бесконечно отправлять запросы с недействительным токеном. Notes и outbox остаются в базе, сообщение объясняет, что сессия закончилась. Возобновление требует нового успешного открытия сессии, а не ожидания ещё одного backoff. В учебном интерфейсе постоянные токены моделируют границу; реальная процедура обновления токена сюда не включена.

Прикладной conflict уже является обработанным ответом. Acknowledge сохраняет remote и state blocked, поэтому следующая итерация pass выбирает другую queued запись. Пытаться решить conflict более частым retry бессмысленно: тот же opId вернёт тот же ledger ответ. Для этой заметки нужна явная операция выбора из предыдущего урока.

Ошибка 422 также не означает, что полезно без конца повторять заведомо неправильный payload. Конечный учебный планировщик показывает HTTP статус и оставляет данные для диагностики, повторяя общий проход. Для рабочего продукта следует отделить постоянные ошибки контракта в самостоятельное blocked состояние и предложить исправление. Такое расширение требует определённой серверной классификации ошибок, а не догадки по любому неуспешному статусу.

Online предлагает попытку, а не обещание

Слушатель online вызывает sync.run. Если сервер всё ещё недоступен из-за DNS, закрытого процесса или отсутствия разрешения, fetch снова завершится ошибкой. Текст интерфейса не должен заранее меняться на «всё согласовано» при самом событии. Согласованность относится к завершённым push/pull и очереди, а не к наличию интерфейса сети в операционной системе.

Учебная галочка offlineDemo блокирует новые обращения внутри api. Она полезна для разбора локальных сценариев без изменения всей сети компьютера. Уже отправленный запрос она не отменяет, поэтому после включения возможно позднее подтверждение. Это ограничение прямо записано в README. В настоящем отключении клиент также не контролирует точную границу принятия сообщения сервером.

Форма при этом продолжает сохранять через IndexedDB. Появление сообщения о сервере не отменяет успешную локальную транзакцию. Если приложение использует один общий status, как наша лаборатория, позднее сообщение может заменить прежнее «Сохранено локально». Поэтому подписи каждой заметки и сохранённая очередь дают дополнительное устойчивое объяснение состояния.

Прогресс связан с прикладным состоянием

Во время отправки notify показывает noteId текущего снимка. После pull сообщает nextCursor, число элементов outbox и наличие продолжения. Для пользователя более привычно показать количество ожидающих записей и отдельное число конфликтов, однако учебная строка делает связь с контрактом явной. Число queued и число всех outbox не совпадают, если в аккаунте есть blocked намерения.

Нельзя отображать сто процентов по количеству успешных fetch, если на сервере остаётся hasMore либо пользователь создал новую локальную редакцию. Общий набор работы меняется во время прохода. Поэтому конечный пример не рисует искусственную шкалу с заранее известным знаменателем: сообщает завершённую страницу и наличие оставшейся работы. Такой прогресс соответствует доступному знанию клиента.

После остановки сессии timer очищается, stopped запрещает новые проходы. Закрытие вкладки освобождает ресурсы браузера, но не очищает notes/outbox. Следующая сессия того же аккаунта снова прочитает очередь и продолжит. Не следует использовать beforeunload как единственное место сохранения текста: событие может не произойти, а наша форма уже сохраняет данные до закрытия.

Теперь недоступный сервер становится управляемым состоянием, а не причиной потери работы или непрерывного запроса. Планировщик ограничивает один проход, сохраняет намерения, отличает требуемый выбор от временного ожидания и сообщает конкретный прогресс. Следующий урок перенесёт ту же дисциплину на несколько вкладок и независимые устройства.

Оглавление серии