Обработка неудачных повторных попыток

Retry-логика и её важность

В веб-приложениях, использующих Service Worker для кэширования и оффлайн-доступа, частой проблемой является временная недоступность ресурсов из-за сетевых ошибок. Workbox предоставляет встроенные механизмы для управления повторными попытками запросов, что позволяет повысить надежность приложения без ручного написания сложной логики. Основная цель — уменьшение числа отказов при загрузке ресурсов и улучшение пользовательского опыта.

Стратегии повторных попыток

Workbox включает модуль workbox-strategies, который позволяет настраивать поведение повторных запросов через объект RetryableResponse и опции стратегий.

Пример базовой конфигурации повторных попыток:

import { NetworkFirst } from 'workbox-strategies';
import { registerRoute } from 'workbox-routing';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

const networkFirstStrategy = new NetworkFirst({
  cacheName: 'dynamic-resources',
  networkTimeoutSeconds: 5,
  plugins: [
    new ExpirationPlugin({
      maxEntries: 100,
      maxAgeSeconds: 24 * 60 * 60
    }),
    new CacheableResponsePlugin({
      statuses: [0, 200]
    })
  ]
});

registerRoute(
  ({ request }) => request.destination === 'script' || request.destination === 'style',
  networkFirstStrategy
);

В этом примере NetworkFirst стратегия сначала пытается загрузить ресурс из сети. Если сетевой запрос не удается в течение 5 секунд, используется кэш. Плагин CacheableResponsePlugin гарантирует, что в кэш попадут только успешные ответы, а ExpirationPlugin управляет ограничениями хранения.

Использование RetryPlugin для автоматизации повторов

Workbox предоставляет workbox-background-sync, который позволяет выполнять повторные попытки асинхронно, даже если пользователь покидает страницу. Для сетевых запросов в реальном времени можно использовать сторонние плагины, реализующие retry-логику.

Пример использования RetryPlugin для управления повторными запросами:

import { StaleWhileRevalidate } from 'workbox-strategies';
import { registerRoute } from 'workbox-routing';
import { Plugin } from 'workbox-core';

class RetryPlugin extends Plugin {
  constructor({ maxRetries = 3, retryDelay = 1000 }) {
    super();
    this.maxRetries = maxRetries;
    this.retryDelay = retryDelay;
  }

  async fetchDidFail({ request }) {
    let attempt = 0;
    while (attempt < this.maxRetries) {
      attempt++;
      try {
        await new Promise(resolve => setTimeout(resolve, this.retryDelay));
        const response = await fetch(request);
        if (response.ok) return response;
      } catch (err) {
        continue;
      }
    }
    throw new Error(`Resource ${request.url} failed after ${this.maxRetries} attempts`);
  }
}

registerRoute(
  ({ request }) => request.destination === 'image',
  new StaleWhileRevalidate({
    cacheName: 'images-cache',
    plugins: [new RetryPlugin({ maxRetries: 5, retryDelay: 2000 })]
  })
);

В этом примере при сбое загрузки изображения запрос повторяется до 5 раз с интервалом в 2 секунды. Если все попытки неудачны, выбрасывается исключение, которое может быть обработано другими плагинами или логикой Service Worker.

Комбинация с Background Sync

Для ресурсов, критически важных для работы приложения (например, отправка форм или данных пользователя), лучше использовать Background Sync:

import { BackgroundSyncPlugin } from 'workbox-background-sync';
import { NetworkOnly } from 'workbox-strategies';
import { registerRoute } from 'workbox-routing';

const bgSyncPlugin = new BackgroundSyncPlugin('formQueue', {
  maxRetentionTime: 24 * 60 // минуты
});

registerRoute(
  /\/api\/submit-form/,
  new NetworkOnly({
    plugins: [bgSyncPlugin]
  }),
  'POST'
);

Background Sync автоматически повторяет отправку запроса при восстановлении сети. Это позволяет полностью избежать потери данных при временном отсутствии соединения, в отличие от простых таймаутов и retry-плагинов.

Логирование и отладка

Для понимания поведения повторных попыток полезно логировать события fetchDidFail, sync и responseWillBeUsed. Workbox поддерживает добавление кастомных логов через плагины:

class LoggingPlugin extends Plugin {
  async fetchDidFail({ request }) {
    console.warn(`Fetch failed for ${request.url}`);
  }

  async syncDidComplete({ queue }) {
    console.info(`Background Sync completed for ${queue.name}`);
  }
}

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

Настройка стратегии под разные типы ресурсов

  • JS и CSS: часто применяют NetworkFirst с retry и кэшем для оффлайн-доступа.
  • Изображения: StaleWhileRevalidate + RetryPlugin для визуальных элементов, где некритична последняя версия.
  • API-запросы POST: NetworkOnly + Background Sync для сохранения данных пользователей.

Такой подход обеспечивает гибкость и минимизирует потерю данных при сетевых сбоях.

Резюме принципов

  1. Повторные попытки помогают смягчить кратковременные сетевые сбои.
  2. Плагины Workbox позволяют управлять retry-поведением через fetchDidFail и Background Sync.
  3. Комбинация стратегий и плагинов дает контроль над кэшированием и надежностью приложения.
  4. Логирование и мониторинг — обязательная часть отладки сложных стратегий повторных попыток.

Обработка неудачных повторных попыток — ключевой инструмент для стабильности современных PWA, особенно при работе с мобильными сетями с нестабильным соединением.