Es posible que algunos sitios web deban comunicarse con el trabajador de servicio sin necesidad de ser informados sobre el resultado. Estos son algunos ejemplos:
- Una página envía al service worker una lista de URLs que debe solicitarse previamente, de modo que, cuando el usuario haga clic en un vínculo, los subrecursos de la página o el documento ya estén disponibles en la caché, lo que acelera la navegación posterior.
- La página le pide al service worker que recupere y almacene en caché un conjunto de artículos principales para que estén disponibles sin conexión.
Delegar estos tipos de tareas no críticas al trabajador de servicio tiene el beneficio de liberar el subproceso principal para controlar mejor las tareas más urgentes, como responder a las interacciones del usuario.
En esta guía, exploraremos cómo implementar una técnica de comunicación unidireccional desde la página al trabajador de servicio con las APIs de navegador estándar y la biblioteca de Workbox. Llamaremos a estos tipos de casos de uso caché imperativo.
Caso de producción
1-800-Flowers.com implementó el almacenamiento en caché imperativo (precarga) con trabajadores del servicio a través de postMessage()
para precargar los elementos principales de las páginas de categorías y acelerar la navegación posterior a las páginas de detalles de los productos.
Usan un enfoque mixto para decidir qué elementos recuperar previamente:
- En el momento de la carga de la página, le pide al trabajador del servicio que recupere los datos JSON de los 9 elementos principales y que agregue los objetos de respuesta resultantes a la caché.
- En el caso de los elementos restantes, escuchan el evento
mouseover
, de modo que, cuando un usuario mueva el cursor sobre un elemento, pueda activar la recuperación del recurso a pedido.
Usan la API de Cache para almacenar respuestas JSON:
Cuando el usuario hace clic en un elemento, los datos JSON asociados con él se pueden recuperar de la caché, sin necesidad de ir a la red, lo que hace que la navegación sea más rápida.
Cómo usar Workbox
Workbox proporciona una manera fácil de enviar mensajes a un service worker, a través del paquete workbox-window
, un conjunto de módulos diseñados para ejecutarse en el contexto de la ventana. Son un complemento de los otros paquetes de Workbox
que se ejecutan en el trabajador de servicio.
Para comunicar la página con el trabajador de servicio, primero obtén una referencia de objeto de Workbox al trabajador de servicio registrado:
const wb = new Workbox('/sw.js');
wb.register();
Luego, puedes enviar el mensaje directamente de forma declarativa, sin la molestia de obtener el registro, verificar la activación ni pensar en la API de comunicación subyacente:
wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });
El service worker implementa un controlador message
para escuchar estos mensajes. De manera opcional, puede mostrar una respuesta, aunque, en casos como estos, no es necesario:
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'PREFETCH') {
// do something
}
});
Uso de las APIs del navegador
Si la biblioteca de Workbox no es suficiente para tus necesidades, aquí te mostramos cómo puedes implementar la comunicación de ventana a service worker usando las APIs del navegador.
La API de postMessage se puede usar para establecer un mecanismo de comunicación unidireccional desde la página al trabajador de servicio.
La página llama a postMessage()
en la interfaz del servicio de trabajo:
navigator.serviceWorker.controller.postMessage({
type: 'MSG_ID',
payload: 'some data to perform the task',
});
El trabajador de servicio implementa un controlador message
para escuchar estos mensajes.
self.addEventListener('message', (event) => {
if (event.data && event.data.type === MSG_ID) {
// do something
}
});
El atributo {type : 'MSG_ID'}
no es absolutamente necesario, pero es una forma de permitir que la página envíe diferentes tipos de instrucciones al trabajador de servicio (es decir, "para recuperar previamente" en comparación con "para borrar el almacenamiento"). El trabajador del servicio puede bifurcarse en diferentes instrucciones de ejecución según esta marca.
Si la operación se realizó correctamente, el usuario podrá obtener los beneficios, pero, de lo contrario, no se alterará el flujo de usuarios principal. Por ejemplo, cuando 1-800-Flowers.com intenta almacenar en caché previamente, la página no necesita saber si el trabajador de servicio tuvo éxito. Si es así, el usuario disfrutará de una navegación más rápida. De lo contrario, la página aún debe navegar a la nueva página. Solo tardará un poco más.
Un ejemplo simple de carga previa
Una de las aplicaciones más comunes de la caché imperativa es la recuperación anticipada, que consiste en recuperar recursos para una URL determinada antes de que el usuario se dirija a ella para acelerar la navegación.
Existen diferentes formas de implementar la carga previa en los sitios:
- Usar etiquetas de precarga de vínculos en las páginas: Los recursos se mantienen en la caché del navegador durante cinco minutos, después de los cuales se aplican las reglas normales de
Cache-Control
para el recurso. - Complementar la técnica anterior con una estrategia de almacenamiento en caché del tiempo de ejecución en el trabajador del servicio para extender la vida útil del recurso de precarga más allá de este límite
Para situaciones de carga previa relativamente simples, como la carga previa de documentos o recursos específicos (JS, CSS, etc.), esas técnicas son el mejor enfoque.
Si se requiere lógica adicional, por ejemplo, analizar el recurso de precarga (un archivo o una página JSON) para recuperar sus URLs internas, es más apropiado delegar esta tarea por completo al trabajador de servicio.
Delegar estos tipos de operaciones al trabajador de servicio tiene las siguientes ventajas:
- Descargar el trabajo pesado de la recuperación y el procesamiento posterior a la recuperación (que se presentará más adelante) a un subproceso secundario De esta manera, se libera el subproceso principal para que controle tareas más importantes, como responder a las interacciones del usuario.
- Permitir que varios clientes (p.ej., pestañas) reutilicen una funcionalidad común, y hasta que llamen al servicio simultáneamente sin bloquear el subproceso principal.
Precarga de páginas de detalles del producto
Primero, usa postMessage()
en la interfaz del trabajador de servicio y pasa un array de URLs para almacenar en caché:
navigator.serviceWorker.controller.postMessage({
type: 'PREFETCH',
payload: {
urls: [
'www.exmaple.com/apis/data_1.json',
'www.exmaple.com/apis/data_2.json',
],
},
});
En el trabajador de servicio, implementa un controlador message
para interceptar y procesar los mensajes que envía cualquier pestaña activa:
addEventListener('message', (event) => {
let data = event.data;
if (data && data.type === 'PREFETCH') {
let urls = data.payload.urls;
for (let i in urls) {
fetchAsync(urls[i]);
}
}
});
En el código anterior, presentamos una pequeña función auxiliar llamada fetchAsync()
para iterar en el array de URLs y emitir una solicitud de recuperación para cada una de ellas:
async function fetchAsync(url) {
// await response of fetch call
let prefetched = await fetch(url);
// (optionally) cache resources in the service worker storage
}
Cuando se obtiene la respuesta, puedes confiar en los encabezados de almacenamiento en caché del recurso. Sin embargo, en muchos casos, como en las páginas de detalles de productos, los recursos no se almacenan en caché (es decir, tienen un encabezado Cache-control
de no-cache
). En estos casos, puedes anular este comportamiento almacenando el recurso recuperado en la caché del trabajador del servicio. Esto tiene el beneficio adicional de permitir que el archivo se entregue en situaciones sin conexión.
Más allá de los datos JSON
Una vez que los datos JSON se recuperan de un extremo del servidor, a menudo contienen otras URLs que también vale la pena cargar previamente, como una imagen o datos de otro extremo asociados con estos datos de primer nivel.
Supongamos que, en nuestro ejemplo, los datos JSON que se muestran son la información de un sitio de compras de comestibles:
{
"productName": "banana",
"productPic": "https://cdn.example.com/product_images/banana.jpeg",
"unitPrice": "1.99"
}
Modifica el código fetchAsync()
para iterar en la lista de productos y almacenar en caché la imagen hero de cada uno de ellos:
async function fetchAsync(url, postProcess) {
// await response of fetch call
let prefetched = await fetch(url);
//(optionally) cache resource in the service worker cache
// carry out the post fetch process if supplied
if (postProcess) {
await postProcess(prefetched);
}
}
async function postProcess(prefetched) {
let productJson = await prefetched.json();
if (productJson && productJson.product_pic) {
fetchAsync(productJson.product_pic);
}
}
Puedes agregar un control de excepciones alrededor de este código para situaciones como los errores 404. Pero lo mejor de usar un service worker para la carga previa es que puede fallar sin que esto tenga consecuencias para la página y el subproceso principal. También puedes tener una lógica más elaborada en el procesamiento posterior del contenido cargado previamente, lo que lo hace más flexible y está separado de los datos que maneja. Todo es posible.
Conclusión
En este artículo, analizamos un caso de uso común de la comunicación unidireccional entre la página y el trabajador de servicio: la caché imperativa. Los ejemplos que se analizaron solo tienen como objetivo demostrar una forma de usar este patrón, y el mismo enfoque también se puede aplicar a otros casos de uso, por ejemplo, almacenar en caché los artículos principales a pedido para el consumo sin conexión, la creación de favoritos y otros.
Para obtener más patrones de comunicación entre la página y el trabajador de servicio, consulta lo siguiente:
- Broadcast updates: Llama a la página desde el service worker para informar actualizaciones importantes (p.ej., si hay una versión nueva disponible de la app web).
- Comunicación bidireccional: Delega una tarea a un trabajador de servicio (p.ej., una descarga pesada) y mantén a la página informada sobre el progreso.