Transmite tu camino a respuestas inmediatas

Cualquiera que haya usado los service workers podría decirte que son asíncronos hasta el final. Se basan exclusivamente en interfaces basadas en eventos, como FetchEvent, y usan promesas para indicar cuándo se completan las operaciones asíncronas.

La asíncrono es igual de importante, aunque menos visible para el desarrollador, cuando se trata de respuestas que proporciona el controlador de eventos de recuperación de un service worker. Las respuestas de transmisión son el estándar de referencia aquí: permiten que la página que realizó la solicitud original comience a trabajar con la respuesta en cuanto esté disponible el primer fragmento de datos y, posiblemente, use analizadores optimizados para la transmisión a fin de mostrar el contenido de forma progresiva.

Cuando escribes tu propio controlador de eventos fetch, es común simplemente pasar el método respondWith() un Response (o una promesa para un Response) que obtienes a través de fetch() o caches.match() y llamar al día. La buena noticia es que los elementos Response creados por ambos métodos ya se pueden transmitir. La mala noticia es que los Response construidos "manualmente" no se pueden transmitir, al menos hasta ahora. En este punto, la API de Streams entra en juego.

¿Transmisiones?

Una transmisión es una fuente de datos que se puede crear y manipular de forma incremental y proporciona una interfaz para leer o escribir fragmentos de datos asíncronos, de los cuales solo un subconjunto puede estar disponible en la memoria en cualquier momento. Por ahora, nos interesan los ReadableStream, que se pueden usar para construir un objeto Response que se pasa a 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);
});

La página cuya solicitud activó el evento fetch recibirá una respuesta de transmisión en cuanto se llame a event.respondWith(), y seguirá leyendo desde esa transmisión mientras el service worker continúe con enqueue() datos adicionales. La respuesta que fluye del service worker a la página es realmente asíncrona, ¡y tenemos control total sobre cómo completar la transmisión!

Usos en el mundo real

Es probable que hayas notado que el ejemplo anterior tenía algunos comentarios de marcador de posición /* your data here */ y que fue muy sencillo conocer los detalles reales de la implementación. ¿Cómo sería un ejemplo del mundo real?

Jake Archibald (no es de extrañar) tiene un excelente ejemplo de cómo usar transmisiones para unir una respuesta HTML a partir de varios fragmentos de HTML almacenados en caché, junto con datos "en vivo" transmitidos a través de fetch(). En este caso, contenido para su blog

La ventaja de usar una respuesta de transmisión, como explica Jake, es que el navegador puede analizar y renderizar el HTML mientras se transmite, incluido el bit inicial que se carga rápidamente desde la caché, sin tener que esperar a que se complete la recuperación de contenido del blog. De esta manera, se aprovechan al máximo las capacidades de renderización progresiva de HTML del navegador. Otros recursos que también se pueden renderizar de forma progresiva, como algunos formatos de imagen y video, también pueden beneficiarse de este enfoque.

¿Transmisiones? ¿O a los shells de app?

Las prácticas recomendadas existentes sobre el uso de service workers para potenciar tus apps web se centran en un modelo de App Shell + contenido dinámico. Ese enfoque se basa en almacenar en caché de forma agresiva la "shell" de tu aplicación web (el código HTML, JavaScript y CSS mínimos que se necesita para mostrar la estructura y el diseño) y, luego, cargar el contenido dinámico necesario para cada página específica a través de una solicitud del cliente.

Las transmisiones traen consigo una alternativa al modelo de shell de aplicación, una en la que se transmite una respuesta HTML más completa al navegador cuando un usuario navega a una página nueva. La respuesta transmitida puede usar recursos almacenados en caché, por lo que aún puede proporcionar el fragmento inicial de HTML rápidamente, incluso sin conexión, pero termina luciendo más como cuerpos de respuesta tradicionales renderizados por el servidor. Por ejemplo, si tu app web usa la tecnología de un sistema de administración de contenido que el servidor procesa el HTML uniendo plantillas parciales, el modelo se traduce directamente en el uso de respuestas de transmisión, con la lógica de plantillas replicada en el service worker en lugar de tu servidor. Como se muestra en el siguiente video, para ese caso de uso, la ventaja de velocidad que ofrecen las respuestas en tiempo real puede ser sorprendente:

Una ventaja importante de transmitir toda la respuesta HTML (y explicar por qué es la alternativa más rápida del video) es que el HTML renderizado durante la solicitud de navegación inicial puede aprovechar al máximo el analizador de HTML de transmisión del navegador. Los fragmentos de HTML que se insertan en un documento después de que se carga la página (como suele suceder en el modelo de App Shell) no pueden aprovechar esta optimización.

Por lo tanto, si te encuentras en las etapas de planificación de la implementación del service worker, ¿qué modelo deberías adoptar: las respuestas transmitidas que se renderizan de forma progresiva o una shell liviana junto con una solicitud de contenido dinámico del cliente? La respuesta es, como es de esperarse, que depende: de si tienes una implementación existente que se basa en un CMS y plantillas parciales (ventaja: transmisión), de si esperas cargas útiles de HTML grandes y únicas que se beneficiarían del procesamiento progresivo (ventaja: transmisión), de si tu app web se modela mejor como una aplicación de una sola página (ventaja: App Shell) y de si necesitas un modelo que sea compatible actualmente con App Shell en varios navegadores.

Aún estamos en las primeras etapas de las respuestas de transmisión con tecnología de service worker y esperamos ver el avance de los diferentes modelos y, en especial, ver más herramientas desarrolladas para automatizar casos de uso comunes.

Explora en detalle las transmisiones

Si construyes tus propias transmisiones legibles, solo llamar a controller.enqueue() de manera indiscriminada podría no ser suficiente o eficiente. Jake explica algunos detalles sobre cómo se pueden usar en conjunto los métodos start(), pull() y cancel() para crear un flujo de datos personalizado para tu caso de uso.

Si quieres obtener aún más detalles, puedes consultar la especificación de transmisiones.

Compatibilidad

Se agregó compatibilidad para construir un objeto Response dentro de un service worker mediante ReadableStream como fuente en Chrome 52.

La implementación de service worker de Firefox todavía no admite respuestas respaldadas por ReadableStream, pero hay un error de seguimiento relevante sobre la compatibilidad con la API de Streams que puedes seguir.

El progreso de la compatibilidad de la API de Streams sin prefijo en Edge, junto con la compatibilidad general de los service worker, se puede seguir en la página de estado de la plataforma de Microsoft.