Hasta ahora, solo había menciones y pequeños fragmentos de código de la interfaz Cache
.
Para usar service workers de manera eficaz, es necesario adoptar una o más estrategias de almacenamiento en caché, lo que requiere un poco de familiaridad con la interfaz Cache
.
Una estrategia de almacenamiento en caché es una interacción entre el evento fetch
de un service worker y la interfaz Cache
.
La forma en que se escribe una estrategia de almacenamiento en caché depende; por ejemplo, puede ser preferible manejar las solicitudes de elementos estáticos de manera diferente a los documentos, y esto afecta la forma en que se compone una estrategia de almacenamiento en caché.
Antes de pasar a las estrategias en sí, tomémonos un segundo para hablar de lo que no es la interfaz Cache
, qué es y un resumen rápido de algunos de los métodos que ofrece para administrar las memorias caché de los service worker.
La interfaz Cache
en comparación con la caché HTTP
Si nunca trabajaste con la interfaz Cache
, puede ser tentador pensar que es lo mismo que la caché HTTP o, al menos, que está relacionado con ella. Este no es el caso.
- La interfaz
Cache
es un mecanismo de almacenamiento en caché completamente independiente de la caché HTTP. - Cualquier configuración de
Cache-Control
que uses para influir en la caché HTTP no influye en los elementos que se almacenan en la interfazCache
.
Resulta útil pensar en las cachés del navegador como en capas. La caché HTTP es una caché de bajo nivel controlada por pares clave-valor con directivas expresadas en encabezados HTTP.
Por el contrario, la interfaz Cache
es una caché de alto nivel controlada por una API de JavaScript.
Esto ofrece más flexibilidad que cuando se usan pares clave-valor HTTP relativamente simples y es la mitad de lo que hace posibles las estrategias de almacenamiento en caché.
Estos son algunos métodos de API importantes para las cachés de service worker:
CacheStorage.open
para crear una nueva instancia deCache
.Cache.add
yCache.put
para almacenar respuestas de red en la caché de un service worker.Cache.match
para ubicar una respuesta almacenada en caché en una instancia deCache
.Cache.delete
para quitar una respuesta almacenada en caché de una instancia deCache
.
Esos son solo algunos. Existen otros métodos útiles, pero estos son los básicos que verás más adelante en esta guía.
El modesto evento fetch
La otra mitad de una estrategia de almacenamiento en caché es el evento fetch
del service worker.
Hasta ahora, en esta documentación, escuchaste un poco sobre la “intercepción de solicitudes de red”, y el evento fetch
dentro de un service worker es donde esto sucede:
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(cacheName));
});
self.addEventListener('fetch', async (event) => {
// Is this a request for an image?
if (event.request.destination === 'image') {
// Open the cache
event.respondWith(caches.open(cacheName).then((cache) => {
// Respond with the image from the cache or from the network
return cache.match(event.request).then((cachedResponse) => {
return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
// Add the network response to the cache for future visits.
// Note: we need to make a copy of the response to save it in
// the cache and use the original as the request response.
cache.put(event.request, fetchedResponse.clone());
// Return the network response
return fetchedResponse;
});
});
}));
} else {
return;
}
});
Este es un ejemplo sencillo, que puedes ver en acción, pero que ofrece una idea de lo que pueden hacer los service workers. El código anterior hace lo siguiente:
- Inspecciona la propiedad
destination
de la solicitud para ver si se trata de una solicitud de imagen. - Si la imagen está en la caché del service worker, puedes entregarla desde allí. De lo contrario, recupera la imagen de la red, almacena la respuesta en la caché y muestra la respuesta de la red.
- Todas las demás solicitudes se pasan a través del service worker sin interacción con la caché.
El objeto event
de una recuperación contiene una propiedad request
con fragmentos de información útiles que te ayudarán a identificar el tipo de cada solicitud:
url
, que es la URL de la solicitud de red que controla el eventofetch
en este momentomethod
, que es el método de solicitud (p.ej.,GET
oPOST
).mode
, que describe el modo de la solicitud Por lo general, se usa un valor de'navigate'
para distinguir las solicitudes de documentos HTML de otras solicitudes.destination
, que describe el tipo de contenido que se solicita de una manera que evita el uso de la extensión de archivo del elemento solicitado.
Una vez más, asincronía es el nombre del juego.
Recuerda que el evento install
ofrece un método event.waitUntil
que toma una promesa y espera a que se resuelva antes de continuar con la activación.
El evento fetch
ofrece un método event.respondWith
similar que puedes usar para mostrar el resultado de una solicitud fetch
asíncrona o una respuesta que muestra el método match
de la interfaz Cache
.
Estrategias de almacenamiento en caché
Ahora que estás familiarizado con las instancias de Cache
y el controlador de eventos fetch
, estás listo para profundizar en algunas estrategias de almacenamiento en caché del service worker.
Si bien las posibilidades son prácticamente ilimitadas, en esta guía, te enfocaremos en las estrategias que se incluyen en Workbox para que puedas tener una idea de lo que sucede en su funcionamiento.
Solo caché
Comencemos con una estrategia de almacenamiento en caché simple que llamaremos "Solo caché". Es decir, cuando el service worker tenga el control de la página, las solicitudes coincidentes solo irán a la caché. Esto significa que cualquier elemento almacenado en caché deberá almacenarse previamente en caché para estar disponible para que el patrón funcione, y esos elementos nunca se actualizarán en la caché hasta que se actualice el service worker.
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
// Assets to precache
const precachedAssets = [
'/possum1.jpg',
'/possum2.jpg',
'/possum3.jpg',
'/possum4.jpg'
];
self.addEventListener('install', (event) => {
// Precache assets on install
event.waitUntil(caches.open(cacheName).then((cache) => {
return cache.addAll(precachedAssets);
}));
});
self.addEventListener('fetch', (event) => {
// Is this one of our precached assets?
const url = new URL(event.request.url);
const isPrecachedRequest = precachedAssets.includes(url.pathname);
if (isPrecachedRequest) {
// Grab the precached asset from the cache
event.respondWith(caches.open(cacheName).then((cache) => {
return cache.match(event.request.url);
}));
} else {
// Go to the network
return;
}
});
Arriba, se muestra un array de elementos que se almacena en caché previamente en el momento de la instalación.
Cuando el service worker controla las recuperaciones,
comprobamos si la URL de solicitud que controla el evento fetch
está en el array de los elementos almacenados en caché previamente.
Si es así, tomamos el recurso de la caché y omitimos la red.
Otras solicitudes pasan a través de la red, y solo de esta.
Para ver esta estrategia en acción, consulta esta demostración con tu consola abierta.
Solo de red
Lo contrario de “Solo caché” es “Solo red”, en el que una solicitud pasa a través de un service worker a la red, sin ninguna interacción con su caché. Esta es una buena estrategia para garantizar la actualización del contenido (piensa en el lenguaje de marcado), pero la desventaja es que nunca funcionará cuando el usuario no tenga conexión.
Garantizar que una solicitud pase a la red solo significa que no se llama a event.respondWith
para una solicitud coincidente.
Si deseas ser explícito, puedes colocar un return;
vacío en la devolución de llamada de evento fetch
para las solicitudes que quieras pasar a la red.
Esto es lo que sucede en la demostración de la estrategia “Solo caché” para las solicitudes que no se almacenan previamente en la caché.
Caché primero, y recurrir a la red
En esta estrategia, las cosas se vuelven un poco más involucradas. Para las solicitudes coincidentes, el proceso es el siguiente:
- La solicitud llega a la caché. Si el recurso está en la caché, publícalo desde allí.
- Si la solicitud no está en la caché, ve a la red.
- Una vez finalizada la solicitud de red, agrégala a la caché y, luego, muestra la respuesta de la red.
A continuación, verás un ejemplo de esta estrategia, que puedes probar en una demostración en vivo:
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
self.addEventListener('fetch', (event) => {
// Check if this is a request for an image
if (event.request.destination === 'image') {
event.respondWith(caches.open(cacheName).then((cache) => {
// Go to the cache first
return cache.match(event.request.url).then((cachedResponse) => {
// Return a cached response if we have one
if (cachedResponse) {
return cachedResponse;
}
// Otherwise, hit the network
return fetch(event.request).then((fetchedResponse) => {
// Add the network response to the cache for later visits
cache.put(event.request, fetchedResponse.clone());
// Return the network response
return fetchedResponse;
});
});
}));
} else {
return;
}
});
Aunque este ejemplo solo abarca imágenes, esta es una excelente estrategia para aplicar a todos los recursos estáticos (como CSS, JavaScript, imágenes y fuentes), especialmente a los que tienen versiones de hash. Ofrece un aumento de velocidad para los elementos inmutables evitando las verificaciones de actualización del contenido con el servidor que la caché HTTP puede iniciarse. Lo más importante es que los elementos almacenados en caché estarán disponibles sin conexión.
Primero de la red y recurrir a la caché
Si quisieras girar la opción “Caché primero, luego la red en segundo lugar” y terminarás con la estrategia “Red primero, caché en segundo lugar”, que es como suena:
- Primero, vas a la red para obtener una solicitud y colocas la respuesta en la caché.
- Si estás sin conexión más adelante, recurrirás a la última versión de esa respuesta en la caché.
Esta estrategia es excelente para solicitudes HTML o a la API cuando, mientras estás en línea, deseas la versión más reciente de un recurso, pero deseas otorgar acceso sin conexión a la versión disponible más reciente. A continuación, se muestra cómo podría verse cuando se aplique a las solicitudes de HTML:
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
self.addEventListener('fetch', (event) => {
// Check if this is a navigation request
if (event.request.mode === 'navigate') {
// Open the cache
event.respondWith(caches.open(cacheName).then((cache) => {
// Go to the network first
return fetch(event.request.url).then((fetchedResponse) => {
cache.put(event.request, fetchedResponse.clone());
return fetchedResponse;
}).catch(() => {
// If the network is unavailable, get
return cache.match(event.request.url);
});
}));
} else {
return;
}
});
Puedes probar esto en una demostración. Primero, ve a la página. Es posible que debas volver a cargar la página para que la respuesta HTML se coloque en la caché. Luego, en las herramientas para desarrolladores, simula una conexión sin conexión y vuelve a cargar la página. La última versión disponible se entregará instantáneamente desde la caché.
En situaciones en las que la capacidad sin conexión es importante, pero necesitas equilibrar esa capacidad con el acceso a la versión más reciente de un poco de lenguaje de marcado o datos de API, la estrategia "Red primero, caché en segundo lugar" es una estrategia sólida que logra ese objetivo.
Revalidación inactiva durante el período de inactividad
De las estrategias que tratamos hasta ahora, la etapa “Stale-while-revalidate” es la más compleja. Es similar a las dos últimas estrategias en algunos aspectos, pero el procedimiento prioriza la velocidad de acceso de un recurso y, al mismo tiempo, lo mantiene actualizado en segundo plano. Esta estrategia es como la siguiente:
- En la primera solicitud de un elemento, recupéralo de la red, colócalo en la caché y muestra la respuesta de la red.
- En solicitudes posteriores, entrega el recurso desde la caché primero y, luego, “en segundo plano”, vuelve a solicitarlo en la red y actualiza la entrada de caché del elemento.
- Para las solicitudes posteriores, recibirás la última versión recuperada de la red que se colocó en la caché en el paso anterior.
Esta es una estrategia excelente para cosas que son otro importantes para mantenerse actualizados, pero no son cruciales. Piensa en cosas como avatares para un sitio de redes sociales. Se actualizan cuando los usuarios lo hacen, pero la versión más reciente no es estrictamente necesaria en cada solicitud.
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'image') {
event.respondWith(caches.open(cacheName).then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
const fetchedResponse = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return cachedResponse || fetchedResponse;
});
}));
} else {
return;
}
});
Puedes ver esto en acción en otra demostración en vivo, sobre todo si prestas atención a la pestaña Red en las herramientas para desarrolladores de tu navegador y a su visor CacheStorage
(si las herramientas para desarrolladores de tu navegador tienen esa herramienta).
Pasemos a Workbox
En este documento, se concluye nuestra revisión de la API de service worker, así como de las APIs relacionadas, lo que significa que aprendiste lo suficiente sobre cómo usar los service workers de forma directa para comenzar a experimentar con Workbox.