Service worker: Estrategias de caché y modo offline

Compartir esta publicación

En este artículo vamos a ver una de las funciones del service worker como estrategias de caché y  modo offline.

Para empezar, necesitaremos un servidor de recursos. En Apiumhub hemos creado un pequeño servidor de ejemplo desde donde serviremos nuestros recursos html, js, css, etc., está hecho en node por su sencillez. También contiene los archivos del service worker que veremos a continuación. LINK

 

Estrategias de caché y modo offline

Combinando el uso de fetch API y cache API, podemos crear diferentes estrategias de caché para nuestro service workers. Algunas de las más comunes las detallamos a continuación.

 

Cache first

Esta estrategia responde a las peticiones con la versión del recurso que hay cacheado en el Cache Storage. Si es la primera vez y no encuentra el recurso en caché, nos devolverá el recurso desde la red y lo almacenará en caché para la siguiente vez que lo consultemos.

 

Estrategias de caché

 


const cacheFirst = (event) => {
 event.respondWith(
   caches.match(event.request).then((cacheResponse) => {
     return cacheResponse || fetch(event.request).then((networkResponse) => {
       return caches.open(currentCache).then((cache) => {
         cache.put(event.request, networkResponse.clone());
         return networkResponse;
       })
     })
   })
 )
};

Normalmente, suelo usar esta estrategia para recursos media, como imágenes, videos, etc., ya que son recursos más pesados y tardan más en cargar.

 

Cache only

 

Esta estrategia responde a las peticiones directamente con la versión cacheada del recurso. Si la versión cacheada no existe, devolverá un error.Screenshot 2021 02 16 at 10.31.28

 

 


const cacheOnly = (event) => {
 event.respondWith(caches.match(event.request));
};

Al menos yo, no le he encontrado un caso de uso.

 

Network first

Esta estrategia prioriza la versión más actualizada del recurso intentando siempre obtenerlo primero por red, aunque ya exista una versión en caché. Si la respuesta de red es satisfactoria, actualizará la caché. Si hay algún error en la respuesta de red devolverá el recurso directamente desde caché, si lo hay.

 

Screenshot 2021 02 16 at 10.33.03

 


const networkFirst = (event) => {
 event.respondWith(
   fetch(event.request)
     .then((networkResponse) => {
       return caches.open(currentCache).then((cache) => {
         cache.put(event.request, networkResponse.clone());
         return networkResponse;
       })
     })
     .catch(() => {
       return caches.match(event.request);
     })
 )
};

Normalmente la uso en llamadas a la api, donde la frecuencia de cambio del contenido es muy baja y no es crítico devolver la última versión del contenido. Con esto, priorizo contenido nuevo y doy cabida al modo offline, donde veremos la versión de los datos de nuestra última sesión con conexión.

 

Network only

Esta estrategia prioriza siempre la versión más actualizada del recurso obteniendo siempre dese red. Si la petición falla, devolverá el error.

  Stow: Terminal Alacritty

Screenshot 2021 02 16 at 10.33.56

 


const networkOnly = (event) => {
 event.respondWith(fetch(event.request));
};

Normalmente la uso para llamadas a la api, donde es crítico que la versión de los datos que vamos a mostrar ha de ser la última. Como por ejemplo, si ya se han agotado las unidades de un producto, no deberíamos obtener una respuesta cacheada como { stock: 5 }.

 

Stale while revalidate

Con la combinación de las anteriores estrategias podemos crearnos las nuestras personalizadas, como es el caso de esta.

Esta estrategia prioriza el contenido en caché para cargar los recursos instantáneamente (cache first) y paralelamente hace una petición a red para actualizar el contenido de la caché con la última versión del recurso para futuras peticiones. Si la primera vez no hay nada en caché, hará una petición a red (network first).

Screenshot 2021 02 16 at 10.34.55

 


const staleWhileRevalidate = (event) => {
 event.respondWith(
   caches.match(event.request).then((cacheResponse) => {
     if (cacheResponse) {
       fetch(event.request).then((networkResponse) => {
         return caches.open(currentCache).then((cache) => {
           cache.put(event.request, networkResponse.clone());
           return networkResponse;
         })
       });
       return cacheResponse;
     } else {
       return fetch(event.request).then((networkResponse) => {
         return caches.open(currentCache).then((cache) => {
           cache.put(event.request, networkResponse.clone());
           return networkResponse;
         })
       });
     }
   })
 );
};

Normalmente la uso para los archivos estáticos como css, js, etc. ya que solo cambian cuando se hace un nuevo despliegue de código.

 

Probando nuestro service worker

Ahora que ya conocemos las diferentes estrategias de caché y que podemos crear las nuestras personalizadas, es hora de ver un ejemplo de service worker funcionando.

Nuestro proyecto se compone de los siguientes archivos

Screenshot 2021 02 16 at 10.40.00

 

  • index.html, la página principal desde donde se registrará el service worker.
  • sw.js, es el service worker.
  • sw.strategies.js, contiene las diferentes estrategias implementadas para ser usadas desde nuestro service worker.
  • assets, una carpeta donde están los archivos de imágenes, scripts y estilos.

En index.html tendremos el script para registrar el service worker.

 


if ('serviceWorker' in navigator) {
 window.addEventListener('load', function() {
   navigator.serviceWorker.register('/sw.js').then(function(registration) {
     console.log('SW registration successful with scope: ', registration.scope);
   }, function(err) {
     console.log('SW registration failed: ', err);
   });
 });
}

Si el registro ha funcionado correctamente, podremos ver nuestro service worker registrado como en la imagen siguiente.

 

Screenshot 2021 02 16 at 10.41.29 1

 

Nuestro service worker se compone de dos partes, la primera para capturar las peticiones y/o cachearlas.


const router = {
 find: (url) => router.routes.find(it => url.match(it.url)),
 routes: [
   { url: `^http://apiumhub.com:[0-9]{1,5}
En este artículo vamos a ver una de las funciones del service worker como estrategias de caché y  modo offline.

Para empezar, necesitaremos un servidor de recursos. En Apiumhub hemos creado un pequeño servidor de ejemplo desde donde serviremos nuestros recursos html, js, css, etc., está hecho en node por su sencillez. También contiene los archivos del service worker que veremos a continuación. LINK

 



Estrategias de caché y modo offline

Combinando el uso de fetch API y cache API, podemos crear diferentes estrategias de caché para nuestro service workers. Algunas de las más comunes las detallamos a continuación.  

Cache first

Esta estrategia responde a las peticiones con la versión del recurso que hay cacheado en el Cache Storage. Si es la primera vez y no encuentra el recurso en caché, nos devolverá el recurso desde la red y lo almacenará en caché para la siguiente vez que lo consultemos.   Estrategias de caché  

const cacheFirst = (event) => {
 event.respondWith(
   caches.match(event.request).then((cacheResponse) => {
     return cacheResponse || fetch(event.request).then((networkResponse) => {
       return caches.open(currentCache).then((cache) => {
         cache.put(event.request, networkResponse.clone());
         return networkResponse;
       })
     })
   })
 )
};

Normalmente, suelo usar esta estrategia para recursos media, como imágenes, videos, etc., ya que son recursos más pesados y tardan más en cargar.

 

Cache only

 

Esta estrategia responde a las peticiones directamente con la versión cacheada del recurso. Si la versión cacheada no existe, devolverá un error.Screenshot 2021 02 16 at 10.31.28

 

 


const cacheOnly = (event) => {
 event.respondWith(caches.match(event.request));
};

Al menos yo, no le he encontrado un caso de uso.

 

Network first

Esta estrategia prioriza la versión más actualizada del recurso intentando siempre obtenerlo primero por red, aunque ya exista una versión en caché. Si la respuesta de red es satisfactoria, actualizará la caché. Si hay algún error en la respuesta de red devolverá el recurso directamente desde caché, si lo hay.

 

Screenshot 2021 02 16 at 10.33.03

 


const networkFirst = (event) => {
 event.respondWith(
   fetch(event.request)
     .then((networkResponse) => {
       return caches.open(currentCache).then((cache) => {
         cache.put(event.request, networkResponse.clone());
         return networkResponse;
       })
     })
     .catch(() => {
       return caches.match(event.request);
     })
 )
};

Normalmente la uso en llamadas a la api, donde la frecuencia de cambio del contenido es muy baja y no es crítico devolver la última versión del contenido. Con esto, priorizo contenido nuevo y doy cabida al modo offline, donde veremos la versión de los datos de nuestra última sesión con conexión.

 

Network only

Esta estrategia prioriza siempre la versión más actualizada del recurso obteniendo siempre dese red. Si la petición falla, devolverá el error.

Screenshot 2021 02 16 at 10.33.56

 


const networkOnly = (event) => {
 event.respondWith(fetch(event.request));
};

Normalmente la uso para llamadas a la api, donde es crítico que la versión de los datos que vamos a mostrar ha de ser la última. Como por ejemplo, si ya se han agotado las unidades de un producto, no deberíamos obtener una respuesta cacheada como { stock: 5 }.

 

Stale while revalidate

Con la combinación de las anteriores estrategias podemos crearnos las nuestras personalizadas, como es el caso de esta.

Esta estrategia prioriza el contenido en caché para cargar los recursos instantáneamente (cache first) y paralelamente hace una petición a red para actualizar el contenido de la caché con la última versión del recurso para futuras peticiones. Si la primera vez no hay nada en caché, hará una petición a red (network first).

Screenshot 2021 02 16 at 10.34.55

 


const staleWhileRevalidate = (event) => {
 event.respondWith(
   caches.match(event.request).then((cacheResponse) => {
     if (cacheResponse) {
       fetch(event.request).then((networkResponse) => {
         return caches.open(currentCache).then((cache) => {
           cache.put(event.request, networkResponse.clone());
           return networkResponse;
         })
       });
       return cacheResponse;
     } else {
       return fetch(event.request).then((networkResponse) => {
         return caches.open(currentCache).then((cache) => {
           cache.put(event.request, networkResponse.clone());
           return networkResponse;
         })
       });
     }
   })
 );
};

Normalmente la uso para los archivos estáticos como css, js, etc. ya que solo cambian cuando se hace un nuevo despliegue de código.

 

Probando nuestro service worker

Ahora que ya conocemos las diferentes estrategias de caché y que podemos crear las nuestras personalizadas, es hora de ver un ejemplo de service worker funcionando.

Nuestro proyecto se compone de los siguientes archivos

Screenshot 2021 02 16 at 10.40.00

 

  • index.html, la página principal desde donde se registrará el service worker.
  • sw.js, es el service worker.
  • sw.strategies.js, contiene las diferentes estrategias implementadas para ser usadas desde nuestro service worker.
  • assets, una carpeta donde están los archivos de imágenes, scripts y estilos.

En index.html tendremos el script para registrar el service worker.

 


if ('serviceWorker' in navigator) {
 window.addEventListener('load', function() {
   navigator.serviceWorker.register('/sw.js').then(function(registration) {
     console.log('SW registration successful with scope: ', registration.scope);
   }, function(err) {
     console.log('SW registration failed: ', err);
   });
 });
}

Si el registro ha funcionado correctamente, podremos ver nuestro service worker registrado como en la imagen siguiente.

 

Screenshot 2021 02 16 at 10.41.29 1

 

Nuestro service worker se compone de dos partes, la primera para capturar las peticiones y/o cachearlas.

, handle: strategy.staleWhileRevalidate },
{ url: `^http://apiumhub.com:[0-9]{1,5}/.*\\.html`, handle: strategy.staleWhileRevalidate },
{ url: `^http://apiumhub.com:[0-9]{1,5}/.*\\.css`, handle: strategy.staleWhileRevalidate },
{ url: `^http://apiumhub.com:[0-9]{1,5}/.*\\.js`, handle: strategy.staleWhileRevalidate },
{ url: `^http://apiumhub.com:[0-9]{1,5}/.*\\.jpeg`, handle: strategy.cacheFirst }
]
};
self.addEventListener("fetch", event => {
const found = router.find(event.request.url);
if (found) found.handle(event);
});

Y la segunda, un mecanismo para hacer reset en caso de que necesitemos vaciar la caché por completo si tuviéramos algún cache first. Todos los recursos se almacenarán en una caché v1 hasta que cambiemos manualmente la clave, por v2 por ejemplo.


const currentCache = 'v1'; // ← CHANGE IT TO RESET CACHE
self.addEventListener('activate', event => {
 event.waitUntil(
   caches.keys().then(cacheNames => Promise.all(
     cacheNames
       .filter(cacheName => cacheName !== currentCache)
       .map(cacheName => caches.delete(cacheName))
   ))
 );
});

Finalmente, si cargamos la página y todo ha funcionado bien, deberíamos ver nuestros recursos cacheados como en la siguiente imagen.

Screenshot 2021 02 16 at 10.46.04

Y hasta aquí, en el siguiente artículo veremos cómo hacer algo parecido utilizando la librería workbox de google.

Author

  • David Serrano

    Fullstack with more than 17 years of experience in different professional projects. Interested in new technologies and applying best practices for continuous improvement.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Suscríbete a nuestro boletín de noticias

Recibe actualizaciones de los últimos descubrimientos tecnológicos

¿Tienes un proyecto desafiante?

Podemos trabajar juntos

apiumhub software development projects barcelona
Secured By miniOrange