Guía de almacenamiento en caché imperativo

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

Es posible que algunos sitios web deban comunicarse con el service worker informado sobre el resultado. Estos son algunos ejemplos:

  • Una página envía al service worker una lista de URL a de carga previa, de modo que, cuando el usuario haga clic en un vínculo, el documento o los subrecursos de la página ya están disponibles en la caché, lo que facilita a navegar de manera 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 disponer de ellos disponibles sin conexión.

Delegar estos tipos de tareas no críticas al service worker tiene el beneficio de liberar subproceso principal para manejar mejor las tareas más urgentes, como responder a las interacciones del usuario.

Diagrama de una página que solicita recursos para almacenar en caché en un service worker.

En esta guía, exploraremos cómo implementar una técnica de comunicación unidireccional desde la página para usando las APIs del navegador estándar y la biblioteca de Workbox. Estos tipos de los casos de uso, como el almacenamiento en caché imperativo.

Caso de producción

1-800-Flowers.com implementó el almacenamiento en caché imperativo (carga previa) con service workers a través de postMessage() para cargar previamente el artículos principales en las páginas de categorías para acelerar la navegación posterior a las páginas de detalles de los productos.

Logotipo de 1-800 Flowers.

Usan un enfoque mixto para decidir qué elementos cargar previamente:

  • En el momento de cargar la página, le piden al servicer worker que recupere los datos JSON de los 9 elementos principales. agregar los objetos de respuesta resultantes a la caché.
  • Para los elementos restantes, escucha el mouseover. de modo que, cuando un el usuario mueve el cursor sobre un elemento, puede activar la recuperación del recurso a pedido.

Usan la API de Cache para almacenar archivos JSON respuestas:

Logotipo de 1-800 Flowers.
Precargar los datos de productos JSON de las páginas de fichas de productos en 1-800Flowers.com.

Cuando el usuario hace clic en un elemento, los datos JSON asociados a este se pueden obtener de la caché, sin necesidad de acceder a la red, lo que agiliza la navegación.

Uso de Workbox

Workbox ofrece una manera fácil de enviar mensajes a un service worker, a través del paquete workbox-window, un conjunto de módulos para ejecutarse en el contexto de la ventana. Son un complemento de los otros paquetes de Workbox. que se ejecutan en el service worker.

Para comunicar la página con el service worker, primero obtén una referencia de objeto Workbox al service worker 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, la comprobación de la activación o el pensamiento 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 escucha estos mensajes. Opcionalmente, puede mostrar una respuesta, aunque, en casos como estos, es 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 Window to Service la comunicación con los trabajadores con las APIs del navegador.

La API de postMessage se puede usar para establecer un mecanismo de comunicación unidireccional entre la página y el service worker.

La página llama postMessage() en service worker:

navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
  payload: 'some data to perform the task',
});

El service worker implementa un controlador message para escucha estos mensajes.

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === MSG_ID) {
    // do something
  }
});

El atributo {type : 'MSG_ID'} no es absolutamente obligatorio, pero es una forma de permitir que la página enviar diferentes tipos de instrucciones al service worker (es decir, “cargar previamente” frente a “borrar” almacenamiento'). El service worker puede ramificarse en diferentes rutas de ejecución según esta marca.

Si la operación se realizó correctamente, el usuario podrá obtener los beneficios de ella, pero, de no ser así, no alterará el flujo de usuarios principal. Por ejemplo, cuando 1-800-Flowers.com intenta almacenar en caché previamente, no es necesario que la página sepa si el service worker 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 va a tardar un poco más.

Un ejemplo simple de carga previa

Una de las aplicaciones más comunes del almacenamiento en caché imperativo es la carga previa, que significa recuperar para una URL determinada, antes de que el usuario continúe con ella, con el fin de acelerar la navegación.

Existen diferentes maneras de implementar la carga previa en los sitios:

Para situaciones de carga previa relativamente sencillas, como la carga previa de documentos o recursos específicos (JS, CSS, etc.), estas técnicas son el mejor enfoque.

Si se requiere lógica adicional, por ejemplo, analizar el recurso de carga previa (un archivo o una página JSON) en para recuperar sus URLs internas, es más apropiado delegar esta tarea por completo al service worker.

Delegar estos tipos de operaciones al service worker tiene las siguientes ventajas:

  • Transferir la carga pesada del procesamiento de recuperación y posterior (lo que se presentará pronto) más adelante) a un subproceso secundario. De esta manera, se libera el subproceso principal para controlar las funciones tareas como responder a las interacciones del usuario.
  • Permitir que varios clientes (por ejemplo, pestañas) reutilicen una funcionalidad común e incluso llamar al al mismo tiempo sin bloquear el subproceso principal.

Cargar previamente las páginas de detalles de los productos

Primer uso de postMessage() en la interfaz del service worker y pasas un array de URL a la 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 service worker, implementa un controlador message para interceptar y procesar mensajes enviados por 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. En muchos casos, sin embargo, al igual que en las páginas de detalles de los productos, los recursos no se almacenan en caché (es decir, tienen un encabezado Cache-control de no-cache). En casos como estos, puedes anular este comportamiento Almacenar el recurso recuperado en la caché del service worker. Esto tiene el beneficio adicional de permitir que se entregará sin conexión.

Más allá de los datos JSON

Una vez que se recuperan los datos JSON del extremo del servidor, a menudo contienen otras URLs que también se que vale la pena cargar previamente, como una imagen u otros datos de extremo que estén asociados con este de datos no estructurados.

Supongamos que, en nuestro ejemplo, los datos JSON devueltos son la información de un sitio de compra de alimentos:

{
  "productName": "banana",
  "productPic": "https://cdn.example.com/product_images/banana.jpeg",
  "unitPrice": "1.99"
 }

Modifica el código fetchAsync() para iterar la lista de productos y almacenar en caché la hero image 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 algún control de excepciones en 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 ocurra consecuencia a la página y al subproceso principal. También puedes tener una lógica más elaborada procesamiento previo del contenido cargado previamente, lo que lo hace más flexible y está separado de los datos de datos. Todo es posible.

Conclusión

En este artículo, abordamos un caso de uso común de la comunicación unidireccional entre la página y el servicio. trabajador: el almacenamiento en caché imperativo. Los ejemplos analizados solo sirven para mostrar una forma de usando este patrón y el mismo enfoque también se pueden aplicar a otros casos de uso, por ejemplo, almacenar en caché los artículos principales on demand para consumirlos sin conexión, agregarlos a favoritos y otros.

Para ver más patrones de comunicación con la página y el service worker, consulta:

  • Actualizaciones de transmisión: Llama a la página desde el service worker para informar. actualizaciones importantes (p.ej., hay disponible una nueva versión de la aplicación web).
  • Comunicación bidireccional: Es la delegación de una tarea a un service worker (p.ej., una descarga pesada) y mantener la página informada sobre el progreso.