Переопределение fetch-обработчика

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


Основы fetch-обработчика

В Workbox fetch-обработчик используется для перехвата всех запросов, инициированных страницей, и последующей работы с ними через заданные стратегии (Cache First, Network First, Stale While Revalidate и другие). По умолчанию Workbox автоматически регистрирует обработчики для маршрутов через workbox.routing.registerRoute.

Стандартная запись маршрута выглядит так:

import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';

registerRoute(
  ({request}) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images-cache',
  })
);

В этом примере Workbox автоматически создает fetch-обработчик для запросов изображений, применяя стратегию CacheFirst.


Переопределение глобального fetch-обработчика

Для полной кастомизации поведения можно создать собственный fetch-обработчик, который будет обрабатывать все запросы или только их часть. Это делается через self.addEventListener('fetch', ...).

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

self.addEventListener('fetch', (event) => {
  const { request } = event;

  if (request.url.includes('/api/')) {
    event.respondWith(
      fetch(request)
        .then((response) => {
          // Дополнительная обработка ответа
          const clonedResponse = response.clone();
          caches.open('api-cache').then((cache) => {
            cache.put(request, clonedResponse);
          });
          return response;
        })
        .catch(() => caches.match(request))
    );
    return;
  }

  // Для всех остальных запросов использовать стандартную стратегию Workbox
  event.respondWith(workbox.strategies.networkFirst().handle({ request }));
});

Ключевые моменты:

  • event.respondWith() перехватывает ответ на запрос и позволяет вернуть кастомный объект Response.
  • Использование request.clone() обязательно при записи ответа в кэш, чтобы не истощить поток.
  • Можно комбинировать собственную логику с предустановленными стратегиями Workbox через .handle({ request }).

Использование setCatchHandler для глобального управления ошибками

Workbox предоставляет метод workbox.routing.setCatchHandler, который позволяет задать глобальный обработчик ошибок для всех маршрутов. Это особенно полезно при переопределении fetch-логики:

import { setCatchHandler } from 'workbox-routing';

setCatchHandler(async ({ event }) => {
  if (event.request.destination === 'document') {
    return caches.match('/offline.html');
  }
  return Response.error();
});

Пояснения:

  • event.request.destination позволяет определить тип ресурса (document, script, image и т.д.).
  • Возврат Response.error() используется для ресурсов, которые не удалось получить ни из сети, ни из кэша.

Комбинирование маршрутов и кастомного fetch

Для сложных сценариев часто используют комбинацию стандартных маршрутов и переопределенного fetch. Например, можно задать специальные правила для API-запросов и оставить изображения под управлением стандартной стратегии:

import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';

// Стандартный маршрут для изображений
registerRoute(
  ({request}) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images-cache',
  })
);

// Кастомный fetch для API
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/')) {
    event.respondWith(customApiHandler(event.request));
  }
});

async function customApiHandler(request) {
  try {
    const response = await fetch(request);
    const clone = response.clone();
    const cache = await caches.open('api-cache');
    await cache.put(request, clone);
    return response;
  } catch {
    return caches.match(request) || new Response(null, { status: 503 });
  }
}

Особенности работы с потоками и кэшом

При переопределении fetch важно учитывать следующие нюансы:

  1. Потоки ответа и запроса одноразовые: необходимо использовать clone().
  2. Асинхронная запись в кэш не блокирует возврат ответа, что повышает производительность.
  3. Согласованность с Workbox-стратегиями позволяет переиспользовать существующую логику кэширования без дублирования кода.
  4. Обработка ошибок сети должна быть предусмотрена для всех типов ресурсов, чтобы предотвратить падение приложения в офлайн-режиме.

Примеры расширенных сценариев

  1. Кэширование только успешных ответов API:
async function customApiHandler(request) {
  const response = await fetch(request);
  if (response.ok) {
    const clone = response.clone();
    const cache = await caches.open('api-cache');
    await cache.put(request, clone);
  }
  return response;
}
  1. Локальный fallback для изображений при отсутствии сети:
self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(
      fetch(event.request).catch(() => caches.match('/images/fallback.png'))
    );
  }
});
  1. Объединение нескольких стратегий для разных эндпоинтов:
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/news/')) {
    event.respondWith(workbox.strategies.staleWhileRevalidate().handle({ request: event.request }));
  } else if (event.request.url.includes('/static/')) {
    event.respondWith(workbox.strategies.cacheFirst().handle({ request: event.request }));
  }
});

Эти подходы показывают, что переопределение fetch-обработчика в Workbox обеспечивает полную гибкость и контроль над сетевыми запросами, сохраняя при этом возможности встроенных стратегий кэширования и управления офлайн-доступом.