Es posible que algunos sitios web deban comunicarse con el trabajador de servicio sin necesidad de que se les informe sobre el resultado. Estos son algunos ejemplos:
- Una página envía al trabajador de servicio una lista de URLs para la precarga, de modo que, cuando el usuario hace clic en un vínculo, los subrecursos del documento o la página ya están disponibles en la caché, lo que hace que la navegación posterior sea mucho más rápida.
- 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
para que, cuando un usuario mueva el cursor sobre un elemento, pueda activar una 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 forma sencilla de enviar mensajes a un trabajador de servicio a través del paquete workbox-window
, un conjunto de módulos que se diseñaron 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 trabajador de servicio 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
}
});
Cómo usar las APIs del navegador
Si la biblioteca de Workbox no es suficiente para tus necesidades, aquí te mostramos cómo implementar la comunicación del trabajador de ventana a servicio con 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. Si no es así, la página aún debe navegar a la página nueva. Solo tardará un poco más.
Ejemplo simple de la 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.
- Permite que varios clientes (p.ej., pestañas) reutilicen una funcionalidad común y, además, llamen al servicio de forma simultánea 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 se recuperan los datos JSON de un extremo del servidor, a menudo contienen otras URLs que también vale la pena recuperar previamente, como una imagen o algún otro dato de extremo que esté asociado 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. Sin embargo, la ventaja de usar un trabajador de servicio para la carga previa es que puede fallar sin muchas 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 almacenado previamente, lo que lo hace más flexible y desvinculado de los datos que controla. 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:
- Actualizaciones de transmisión: Llamar a la página desde el servicio trabajador para informar sobre actualizaciones importantes (p. ej., hay una versión nueva de la aplicación web disponible)
- 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.