Table of Contents
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.
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.
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.
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.
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).
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
- 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.
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.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.
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.
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.
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).
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
- 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.
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.
Y hasta aquí, en el siguiente artículo veremos cómo hacer algo parecido utilizando la librería workbox de google.
Author
Fullstack with more than 17 years of experience in different professional projects. Interested in new technologies and applying best practices for continuous improvement.
Ver todas las entradas