Entrega

Un aspecto clave de las apps web progresivas es que son confiables; pueden cargar recursos rápidamente, mantener a los usuarios interesados y proporcionar comentarios de inmediato, incluso en condiciones de red deficientes. ¿Cómo puede ser posible? Gracias al evento fetch del trabajador de servicio.

El evento de recuperación

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

El evento fetch nos permite interceptar cada solicitud de red que realiza la AWP en el alcance del trabajador del servicio, tanto para solicitudes del mismo origen como para solicitudes de origen cruzado. Además de las solicitudes de navegación y recursos, la recuperación desde un trabajador de servicio instalado permite que las visitas a la página después de la primera carga de un sitio se rendericen sin llamadas de red.

El controlador fetch recibe todas las solicitudes de una app, incluidas las URLs y los encabezados HTTP, y permite que el desarrollador de la app decida cómo procesarlas.

El trabajador de servicio se encuentra entre el cliente y la red.

Tu trabajador de servicio puede reenviar una solicitud a la red, responder con una respuesta almacenada en caché anteriormente o crear una respuesta nueva. La decisión es tuya. Este es un ejemplo sencillo:

self.addEventListener("fetch", event => {
    console.log(`URL requested: ${event.request.url}`);
});

Cómo responder una solicitud

Cuando llega una solicitud a tu trabajador de servicio, puedes hacer dos cosas: ignorarla, lo que permite que se envíe a la red, o responderla. Responder solicitudes desde tu trabajador de servicio te permite elegir qué se muestra en tu AWP y cómo, incluso cuando el usuario no tiene conexión.

Para responder a una solicitud entrante, llama a event.respondWith() desde un controlador de eventos fetch, de la siguiente manera:

// fetch event handler in your service worker file
self.addEventListener("fetch", event => {
    const response = .... // a response or a Promise of response
    event.respondWith(response);
});

Debes llamar a respondWith() de forma síncrona y mostrar un objeto Response. Sin embargo, no puedes llamar a respondWith() después de que finalice el controlador de eventos de recuperación, como dentro de una llamada asíncrona. Si necesitas esperar a la respuesta completa, puedes pasar una promesa a respondWith() que se resuelva con una respuesta.

Cómo crear respuestas

Gracias a la API de Fetch, puedes crear respuestas HTTP en tu código JavaScript, y esas respuestas se pueden almacenar en caché con la API de Cache Storage y mostrarse como si provenieran de un servidor web.

Para crear una respuesta, crea un nuevo objeto Response y establece su cuerpo y opciones, como el estado y los encabezados:

const simpleResponse = new Response("Body of the HTTP response");

const options = {
   status: 200,
   headers: {
    'Content-type': 'text/html'
   }
};
const htmlResponse = new Response("<b>HTML</b> content", options)

Respuesta desde la caché

Ahora que sabes cómo entregar respuestas HTTP desde un trabajador de servicio, es hora de usar la interfaz de almacenamiento en caché para almacenar recursos en el dispositivo.

Puedes usar la API de almacenamiento en caché para verificar si la solicitud recibida de la AWP está disponible en la caché y, si es así, responder a respondWith() con ella. Para ello, primero debes buscar en la caché. La función match(), disponible en la interfaz caches de nivel superior, busca en todas las tiendas de tu origen o en un solo objeto de caché abierto.

La función match() recibe una solicitud HTTP o una URL como argumento y muestra una promesa que se resuelve con la respuesta asociada con la clave correspondiente.

// Global search on all caches in the current origin
caches.match(urlOrRequest).then(response => {
   console.log(response ? response : "It's not in the cache");
});

// Cache-specific search
caches.open("pwa-assets").then(cache => {
  cache.match(urlOrRequest).then(response => {
    console.log(response ? response : "It's not in the cache");
  });
});

Estrategias de almacenamiento en caché

La entrega de archivos solo desde la caché del navegador no se ajusta a todos los casos de uso. Por ejemplo, el usuario o el navegador pueden expulsar la caché. Por eso, debes definir tus propias estrategias para publicar recursos para tu AWP. No estás limitado a una sola estrategia de almacenamiento en caché. Puedes definir diferentes para diferentes patrones de URL. Por ejemplo, puedes tener una estrategia para los recursos mínimos de la IU, otra para las llamadas a la API y una tercera para las URLs de imágenes y datos. Para ello, lee event.request.url en ServiceWorkerGlobalScope.onfetch y analízalo con expresiones regulares o un patrón de URL. (en el momento de escribir este artículo, el patrón de URL no es compatible con todas las plataformas).

Las estrategias más comunes son las siguientes:

Primero en caché
Primero, busca una respuesta almacenada en caché y, si no encuentra una, recurre a la red.
Prioridad para la red
Primero, solicita una respuesta de la red y, si no se muestra ninguna, busca una respuesta en la caché.
Inactivo durante la revalidación
Envía una respuesta desde la caché, mientras que en segundo plano solicita la versión más reciente y la guarda en la caché para la próxima vez que se solicite el recurso.
Solo de red
Siempre responde con una respuesta de la red o genera errores. La caché nunca se consulta.
Solo caché
Siempre responde con una respuesta de la caché o genera errores. La red nunca se consultará. Los recursos que se entregarán con esta estrategia se deben agregar a la caché antes de que se soliciten.

Caché primero

Con esta estrategia, el trabajador de servicio busca la solicitud coincidente en la caché y muestra la respuesta correspondiente si está almacenada en caché. De lo contrario, recupera la respuesta de la red (de manera opcional, actualiza la caché para llamadas futuras). Si no hay una respuesta de caché ni una respuesta de red, la solicitud fallará. Dado que la publicación de recursos sin ir a la red suele ser más rápida, esta estrategia prioriza el rendimiento sobre la actualización.

La estrategia de caché en primer lugar

self.addEventListener("fetch", event => {
   event.respondWith(
     caches.match(event.request)
     .then(cachedResponse => {
       // It can update the cache to serve updated content on the next request
         return cachedResponse || fetch(event.request);
     }
   )
  )
});

Prioridad para la red

Esta estrategia es el reflejo de la estrategia de caché en primer lugar. Verifica si la solicitud se puede entregar desde la red y, si no es así, intenta recuperarla de la caché. Primero, usa la caché. Si no hay una respuesta de red ni una respuesta de caché, la solicitud fallará. Obtener la respuesta de la red suele ser más lento que obtenerla de la caché. Esta estrategia prioriza el contenido actualizado en lugar del rendimiento.

La estrategia de prioridad de la red

self.addEventListener("fetch", event => {
   event.respondWith(
     fetch(event.request)
     .catch(error => {
       return caches.match(event.request) ;
     })
   );
});

Inactivo durante la revalidación

La estrategia de datos inactivos mientras se vuelve a validar muestra una respuesta almacenada en caché de inmediato y, luego, verifica si hay una actualización en la red y reemplaza la respuesta almacenada en caché si se encuentra una. Esta estrategia siempre realiza una solicitud de red, ya que, incluso si se encuentra un recurso almacenado en caché, intentará actualizar lo que estaba en la caché con lo que se recibió de la red para usar la versión actualizada en la siguiente solicitud. Por lo tanto, esta estrategia te brinda una forma de beneficiarte de la publicación rápida de la estrategia de prioridad de la caché y actualizar la caché en segundo plano.

La estrategia de inactividad durante la revalidación

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
        const networkFetch = fetch(event.request).then(response => {
          // update the cache with a clone of the network response
          const responseClone = response.clone()
          caches.open(url.searchParams.get('name')).then(cache => {
            cache.put(event.request, responseClone)
          })
          return response
        }).catch(function (reason) {
          console.error('ServiceWorker fetch failed: ', reason)
        })
        // prioritize cached response over network
        return cachedResponse || networkFetch
      }
    )
  )
})

Solo de red

La estrategia de solo red es similar al comportamiento de los navegadores sin un trabajador de servicio o la API de Cache Storage. Las solicitudes solo mostrarán un recurso si se puede recuperar de la red. Esto suele ser útil para recursos como solicitudes de API solo en línea.

Estrategia de solo red

Solo caché

La estrategia de solo caché garantiza que las solicitudes nunca se envíen a la red. Todas las solicitudes entrantes se responden con un elemento de caché prepropagado. En el siguiente código, se usa el controlador de eventos fetch con el método match del almacenamiento en caché para responder solo a la caché:

self.addEventListener("fetch", event => {
   event.respondWith(caches.match(event.request));
});

Estrategia de solo almacenamiento en caché.

Estrategias personalizadas

Si bien lo anterior son estrategias de almacenamiento en caché comunes, tú estás a cargo de tu trabajador de servicio y de cómo se manejan las solicitudes. Si ninguno de estos funciona para tus necesidades, crea uno propio.

Por ejemplo, puedes usar una estrategia de prioridad de red con un tiempo de espera para priorizar el contenido actualizado, pero solo si la respuesta aparece dentro de un umbral que establezcas. También puedes combinar una respuesta almacenada en caché con una respuesta de red y compilar una respuesta compleja desde el trabajador de servicio.

Actualiza recursos

Mantener actualizados los recursos almacenados en caché de tu AWP puede ser un desafío. Si bien la estrategia de volver a validar los datos inactivos es una forma de hacerlo, no es la única. En el capítulo de actualizaciones, aprenderás diferentes técnicas para mantener el contenido y los recursos de tu app actualizados.

Recursos