Streams API в контексте сервис-воркера

Streams API позволяет обрабатывать данные по частям, не дожидаясь полной загрузки ресурса. В контексте сервис-воркеров это особенно важно для реализации прогрессивной загрузки, оптимизации кэширования и модификации ответов на лету. Основные интерфейсы, используемые в сервис-воркере, это ReadableStream, WritableStream и TransformStream.


ReadableStream

ReadableStream представляет поток данных, которые можно читать по кускам. Он позволяет:

  • Асинхронно получать данные.
  • Обрабатывать большие ресурсы без полной загрузки в память.
  • Интегрироваться с Fetch API для перехвата ответов.

Пример создания ReadableStream внутри сервис-воркера:

const stream = new ReadableStream({
  start(controller) {
    controller.enqueue(new TextEncoder().encode("Привет, "));
    controller.enqueue(new TextEncoder().encode("мир!"));
    controller.close();
  }
});

Поток можно использовать для создания Response:

const response = new Response(stream, {
  headers: { "Content-Type": "text/plain" }
});

Это позволяет отправлять данные пользователю по мере их генерации, не дожидаясь полной готовности ресурса.


WritableStream

WritableStream используется для записи данных по частям. В сервис-воркере он чаще всего применяется для кэширования или перенаправления данных в другой поток.

Пример записи в поток:

const writableStream = new WritableStream({
  write(chunk) {
    console.log("Записываем:", new TextDecoder().decode(chunk));
  },
  close() {
    console.log("Поток закрыт");
  }
});

const writer = writableStream.getWriter();
writer.write(new TextEncoder().encode("Часть 1"));
writer.write(new TextEncoder().encode("Часть 2"));
writer.close();

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


TransformStream

TransformStream объединяет чтение и запись, обеспечивая возможность модификации данных на лету. В сервис-воркере это особенно удобно для:

  • Сжатия и декомпрессии данных.
  • Фильтрации контента.
  • Логирования или мониторинга перед отправкой ответа.

Пример использования TransformStream:

const upperCaseTransform = new TransformStream({
  transform(chunk, controller) {
    const text = new TextDecoder().decode(chunk);
    controller.enqueue(new TextEncoder().encode(text.toUpperCase()));
  }
});

fetch("/example.txt")
  .then(response => response.body)
  .then(stream => stream.pipeThrough(upperCaseTransform))
  .then(transformedStream => new Response(transformedStream))
  .then(resp => resp.text())
  .then(console.log);

В этом примере весь текст из /example.txt преобразуется в верхний регистр в процессе передачи, без полной загрузки в память.


Интеграция Streams API с кэшированием

Сервис-воркеры часто используют кэш для оптимизации работы веб-приложений. Streams API позволяет кэшировать данные по частям, что особенно важно для больших файлов.

Пример кэширования потока:

self.addEventListener("fetch", event => {
  event.respondWith(
    caches.open("dynamic-cache").then(async cache => {
      const response = await fetch(event.request);
      const { readable, writable } = new TransformStream();
      
      response.body.pipeTo(writable); // поток направляется в TransformStream
      cache.put(event.request, new Response(readable.clone()));
      
      return new Response(readable);
    })
  );
});

Такой подход позволяет одновременно отправлять данные клиенту и сохранять их в кэш, экономя память и ускоряя обработку.


Работа с ошибками и завершением потоков

При работе с потоками важно корректно обрабатывать ошибки:

const stream = new ReadableStream({
  start(controller) {
    try {
      controller.enqueue(new TextEncoder().encode("Данные"));
      controller.close();
    } catch (error) {
      controller.error(error);
    }
  }
});

stream.cancel("Отмена по условию");
  • controller.error() — завершает поток с ошибкой.
  • controller.close() — закрывает поток после завершения передачи данных.
  • stream.cancel() — позволяет отменить передачу, если данные больше не нужны.

Комбинирование Streams API с Workbox

Workbox облегчает создание стратегий кэширования, но встроенная поддержка потоков расширяет возможности:

  1. Промежуточная модификация данных:
import { registerRoute } from 'workbox-routing';

registerRoute(
  ({ request }) => request.destination === 'script',
  async ({ request }) => {
    const response = await fetch(request);
    const upperCaseTransform = new TransformStream({
      transform(chunk, controller) {
        const text = new TextDecoder().decode(chunk);
        controller.enqueue(new TextEncoder().encode(text.toUpperCase()));
      }
    });

    return new Response(response.body.pipeThrough(upperCaseTransform), response);
  }
);
  1. Прогрессивная загрузка больших файлов:
  • Позволяет отдавать клиенту первые части файла сразу.
  • Одновременно кэшировать оставшиеся части.
  • Реализовывать ленивую загрузку ресурсов.
  1. Мониторинг и логирование потоков:
  • Через TransformStream можно собирать статистику о скорости загрузки.
  • Отслеживать ошибки и количество переданных байт.

Streams API в сервис-воркерах открывает возможности для асинхронной, оптимизированной работы с данными, интеграции с кэшем и динамической модификации контента. Использование ReadableStream, WritableStream и TransformStream позволяет создавать сложные и эффективные стратегии доставки ресурсов, особенно в сочетании с Workbox, обеспечивая высокую производительность веб-приложений.