Стримите свой путь к немедленным ответам

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

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

При написании собственного обработчика событий fetch обычно просто передается методуResponseWith respondWith() Response (или обещание для Response ), который вы получаете через fetch() caches.match() , и заканчиваете на этом. Хорошей новостью является то, что Response , созданные обоими этими методами, уже доступны для потоковой передачи! Плохая новость заключается в том, что «вручную» созданные Response не поддерживают потоковую передачу, по крайней мере, до сих пор. Именно здесь на сцену выходит Streams API .

Потоки?

Поток — это источник данных, который можно создавать и манипулировать постепенно , а также предоставляет интерфейс для чтения или записи асинхронных фрагментов данных, только подмножество которых может быть доступно в памяти в любой момент времени. На данный момент нас интересуют ReadableStream , который можно использовать для создания объекта Response , передаваемого в fetchEvent.respondWith() :

self.addEventListener('fetch', event => {
    var stream = new ReadableStream({
    start(controller) {
        if (/* there's more data */) {
        controller.enqueue(/* your data here */);
        } else {
        controller.close();
        }
    });
    });

    var response = new Response(stream, {
    headers: {'content-type': /* your content-type here */}
    });

    event.respondWith(response);
});

Страница, запрос которой инициировал событие fetch , получит потоковый ответ обратно, как только будет вызван event.respondWith() , и будет продолжать чтение из этого потока до тех пор, пока сервис-воркер продолжает enqueue() загружать дополнительные данные. Ответ, поступающий от сервис-воркера на страницу, действительно асинхронен, и мы имеем полный контроль над заполнением потока!

Реальное использование

Вы, наверное, заметили, что в предыдущем примере был заполнитель /* your data here */ комментарии, и в нем не было подробностей фактической реализации. Так как же будет выглядеть реальный пример?

У Джейка Арчибальда (что неудивительно!) есть отличный пример использования потоков для объединения HTML-ответа из нескольких кэшированных фрагментов HTML вместе с «живыми» данными, передаваемыми через fetch() — в данном случае контентом для его блога .

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

Потоки? Или оболочки приложений?

Существующие лучшие практики использования сервис-воркеров для поддержки ваших веб-приложений сосредоточены на модели App Shell + динамического контента . Этот подход основан на агрессивном кэшировании «оболочки» вашего веб-приложения — минимального количества HTML, JavaScript и CSS, необходимого для отображения вашей структуры и макета, — а затем на загрузке динамического контента, необходимого для каждой конкретной страницы, посредством запроса на стороне клиента.

Потоки представляют собой альтернативу модели App Shell, в которой более полный HTML-ответ передается в браузер, когда пользователь переходит на новую страницу. Потоковый ответ может использовать кэшированные ресурсы — поэтому он по-прежнему может быстро предоставить начальный фрагмент HTML, даже в автономном режиме! — но в конечном итоге они больше похожи на традиционные тела ответов, отображаемые на сервере. Например, если ваше веб-приложение работает на системе управления контентом, которая отображает HTML на сервере путем объединения частичных шаблонов, эта модель преобразуется непосредственно в использование потоковой передачи ответов, при этом логика шаблонов реплицируется в сервисном работнике, а не на вашем сервере. Как показано в следующем видео, в этом случае преимущество в скорости, которое обеспечивают потоковые ответы, может быть поразительным:

Одним из важных преимуществ потоковой передачи всего ответа HTML, объясняющим, почему это самая быстрая альтернатива видео, является то, что HTML, отображаемый во время первоначального запроса навигации, может в полной мере использовать преимущества потокового анализатора HTML браузера. Фрагменты HTML, которые вставляются в документ после загрузки страницы (как это обычно бывает в модели App Shell), не могут воспользоваться преимуществами этой оптимизации.

Итак, если вы находитесь на этапе планирования реализации своего сервис-воркера, какую модель вам следует принять: потоковые ответы с постепенной отрисовкой или облегченную оболочку в сочетании с запросом динамического контента на стороне клиента? Ответ, что неудивительно, зависит: от того, есть ли у вас существующая реализация, основанная на CMS и частичных шаблонах (преимущество: поток); от того, ожидаете ли вы одиночные большие полезные данные HTML, которые выиграют от прогрессивного рендеринга (преимущество: поток); о том, лучше всего ли ваше веб-приложение смоделировать как одностраничное (преимущество: App Shell); и о том, нужна ли вам модель, которая в настоящее время поддерживается в стабильных версиях нескольких браузеров (преимущество: App Shell).

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

Погружение глубже в ручьи

Если вы создаете свои собственные читаемые потоки, простой вызов метода controller.enqueue() без разбора может оказаться недостаточным и неэффективным. Джейк подробно рассказывает о том, как методы start() , pull() и cancel() могут использоваться совместно для создания потока данных, адаптированного к вашему варианту использования.

Для тех, кто хочет еще больше подробностей, есть спецификация Streams .

Совместимость

Поддержка создания объекта Response внутри сервис-воркера с использованием ReadableStream в качестве источника была добавлена ​​в Chrome 52 .

Реализация сервис-воркера Firefox пока не поддерживает ответы, поддерживаемые ReadableStream , но существует соответствующая ошибка отслеживания для поддержки Streams API, за которой вы можете следить.

Прогресс в поддержке API Streams без префиксов в Edge, а также общую поддержку сервис-воркеров можно отслеживать на странице состояния платформы Microsoft.