Cómo pensar cuando se piensa en los trabajadores de servicio
Los trabajadores de servicio son potentes y vale la pena aprender a usarlos. Te permiten ofrecer un nivel de experiencia completamente nuevo a tus usuarios. Tu sitio puede cargarse al instante. Puede funcionar sin conexión. Se puede instalar como una app específica de la plataforma y se siente tan pulida, pero con el alcance y la libertad de la Web.
Sin embargo, los trabajadores del servicio no se parecen a nada a lo que la mayoría de los desarrolladores web están acostumbrados. Tienen una curva de aprendizaje pronunciada y algunos inconvenientes que debes tener en cuenta.
Recientemente, Google Developers y yo colaboramos en un proyecto: Service Workies, un juego gratuito para comprender los trabajadores de servicio. Mientras lo compilaba y trabajaba con los complejos detalles de los trabajadores del servicio, me encontré con algunos inconvenientes. Lo que más me ayudó fue idear algunas metáforas descriptivas. En esta publicación, exploraremos estos modelos mentales y analizaremos las características paradójicas que hacen que los trabajadores de servicio sean tan complicados como increíbles.
Lo mismo, pero diferente
Mientras codificas tu trabajador de servicio, muchas cosas te resultarán familiares. Puedes usar tus funciones favoritas del nuevo lenguaje de JavaScript. Escuchas los eventos del ciclo de vida de la misma manera que lo haces con los eventos de la IU. Administras el flujo de control con promesas como de costumbre.
Sin embargo, el comportamiento de otros trabajadores del servicio te hace sentir confundido. Especialmente cuando actualizas la página y no ves los cambios de código aplicados.
Una capa nueva
Por lo general, cuando se crea un sitio, solo debes tener en cuenta dos capas: el cliente y el servidor. El trabajador de servicio es una capa nueva que se encuentra en el medio.
Piensa en tu trabajador de servicio como una especie de extensión del navegador, que tu sitio puede instalar en el navegador del usuario. Una vez instalado, el trabajador de servicio extiende el navegador de tu sitio con una capa intermedia potente. Esta capa de service worker puede interceptar y controlar todas las solicitudes que realiza tu sitio.
La capa del trabajador de servicio tiene su propio ciclo de vida, independiente de la pestaña del navegador. Una simple actualización de página no es suficiente para actualizar un trabajador de servicio, al igual que no esperarías que una actualización de página actualice el código implementado en un servidor. Cada capa tiene sus propias reglas únicas para la actualización.
En el juego Service Workies, abordamos muchos detalles del ciclo de vida del trabajador de servicio y te damos mucha práctica para trabajar con él.
Potente, pero limitado
Tener un service worker en tu sitio te brinda beneficios increíbles. Tu sitio puede hacer lo siguiente:
- funcionan sin problemas incluso cuando el usuario no tiene conexión.
- obtener mejoras masivas de rendimiento mediante el almacenamiento en caché
- usar notificaciones push
- se instalará como una PWA.
A pesar de todo lo que pueden hacer los trabajadores del servicio, están limitados por diseño. No pueden hacer nada de forma síncrona ni en el mismo subproceso que tu sitio. Esto significa que no tendrás acceso a lo siguiente:
- localStorage
- el DOM
- la ventana
La buena noticia es que hay varias formas en que tu página puede comunicarse con su trabajador de servicio, como postMessage
directo, canales de mensajes uno a uno y canales de transmisión uno a muchos.
De larga duración, pero de corta duración
Un trabajador de servicio activo sigue activo incluso después de que un usuario sale de tu sitio o cierra la pestaña. El navegador mantiene este trabajador de servicio para que esté listo la próxima vez que el usuario regrese a tu sitio. Antes de que se realice la primera solicitud, el trabajador de servicio tiene la oportunidad de interceptarla y tomar el control de la página. Esto es lo que permite que un sitio funcione sin conexión: el service worker puede entregar una versión almacenada en caché de la página, incluso si el usuario no tiene conexión a Internet.
En Service Workers, visualizamos este concepto con Kolohe (un service worker amigable) que intercepta y controla las solicitudes.
Detenida
A pesar de que los service workers parecen ser inmortales, se pueden detener en casi cualquier momento. El navegador no quiere desperdiciar recursos en un trabajador de servicio que no está haciendo nada en este momento. Detenerse no es lo mismo que finalizar: el trabajador de servicio permanece instalado y activado. Solo se pone en modo de suspensión. La próxima vez que se necesite (p.ej., para controlar una solicitud), el navegador lo reactivará.
waitUntil
Debido a la posibilidad constante de que se suspenda, tu trabajador de servicio necesita una forma de informarle al navegador cuando está haciendo algo importante y no quiere tomar una siesta. Aquí es donde entra en juego event.waitUntil()
. Este método extiende el ciclo de vida en el que se usa, lo que evita que se detenga y que avance a la siguiente fase de su ciclo de vida hasta que estemos listos. Esto nos da tiempo para configurar las cachés, recuperar recursos de la red, etcétera.
En este ejemplo, se le indica al navegador que nuestro trabajador de servicio no se instalará hasta que se haya creado la caché de assets
y se haya propagado con la imagen de una espada:
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll(["/weapons/sword/blade.png"]);
})
);
});
Ten cuidado con el estado global
Cuando se produce este inicio o detención, se restablece el alcance global del trabajador del servicio. Por lo tanto, ten cuidado de no usar ningún estado global en tu trabajador de servicio, o te arrepentirás la próxima vez que se active y tenga un estado diferente del esperado.
Considera este ejemplo que usa un estado global:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
En cada solicitud, este trabajador de servicio registrará un número, digamos 0.13981866382421893
. La variable hasHandledARequest
también cambia a true
. Ahora, el trabajador de servicio permanece inactivo durante un tiempo, por lo que el navegador lo detiene. La próxima vez que haya una solicitud, se necesitará el trabajador del servicio nuevamente, por lo que el navegador lo activará. Su secuencia de comandos se vuelve a evaluar. Ahora, hasHandledARequest
se restablece a false
y favoriteNumber
es algo completamente diferente: 0.5907281835659033
.
No puedes confiar en el estado almacenado en un trabajador de servicio. Además, crear instancias de elementos como los canales de mensajes puede causar errores: obtendrás una instancia nueva cada vez que se detenga o inicie el trabajador de servicio.
En el capítulo 3 de Service Workers, visualizamos que nuestro trabajador de servicio detenido pierde todo el color mientras espera que se active.
Juntos, pero separados
Solo un service worker puede controlar tu página a la vez. Sin embargo, puede tener dos trabajadores de servicio instalados a la vez. Cuando haces un cambio en el código de tu trabajador de servicio y actualizas la página, en realidad no estás editando el trabajador de servicio. Los service workers son immutable. En cambio, estás creando uno nuevo. Este nuevo trabajador de servicio (llamemos SW2) se instalará, pero aún no se activará. Debe esperar a que finalice el trabajador de servicio actual (SW1) (cuando el usuario salga de tu sitio).
Interferir con las cachés de otro trabajador de servicio
Durante la instalación, SW2 puede configurar los elementos, por lo general, creando y completando cachés. Sin embargo, ten en cuenta que este nuevo trabajador de servicio tiene acceso a todo a lo que tiene acceso el trabajador de servicio actual. Si no tienes cuidado, el nuevo service worker en espera puede causar problemas en el actual. Estos son algunos ejemplos que podrían causarte problemas:
- SW2 podría borrar una caché que SW1 está usando de forma activa.
- SW2 podría editar el contenido de una caché que usa SW1, lo que provocaría que SW1 responda con recursos que la página no espera.
Omitir skipWaiting
Un service worker también puede usar el método skipWaiting()
riesgoso para tomar el control de la página en cuanto se termina de instalar. Por lo general, no es una buena idea, a menos que intentes reemplazar intencionalmente un trabajador de servicio con errores. Es posible que el nuevo trabajador de servicio use recursos actualizados que la página actual no espera, lo que genera errores.
Cómo comenzar con un entorno limpio
La forma de evitar que tus trabajadores del servicio se superpongan es asegurarte de que usen diferentes cachés. La forma más sencilla de hacerlo es crear versiones de los nombres de caché que usan.
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
Cuando implementes un nuevo service worker, actualizarás el version
para que haga lo que necesite con una caché completamente independiente del service worker anterior.
Finaliza la limpieza
Una vez que tu trabajador de servicio alcanza el estado activated
, sabes que se hizo cargo y que el trabajador de servicio anterior es redundante. En este punto, es importante limpiar después del servicio en segundo plano anterior. No solo respeta los límites de almacenamiento en caché de tus usuarios, sino que también puede evitar errores no deseados.
El método caches.match()
es un atajo que se usa con frecuencia para recuperar un elemento de cualquier caché en la que haya una coincidencia. Sin embargo, itera a través de las cachés en el orden en que se crearon. Supongamos que tienes dos versiones de un archivo de secuencia de comandos app.js
en dos cachés diferentes: assets-1
y assets-2
. Tu página espera la secuencia de comandos más reciente que se almacena en assets-2
. Sin embargo, si no borraste la caché anterior, caches.match('app.js')
mostrará la anterior de assets-1
y, lo más probable, es que tu sitio falle.
Para limpiar los trabajadores del servicio anteriores, solo debes borrar la caché que no necesite el nuevo trabajador del servicio:
const version = 2;
const assetCacheName = `assets-${version}`;
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== assetCacheName){
return caches.delete(cacheName);
}
});
);
});
);
});
Evitar que tus service workers se interfieran entre sí requiere un poco de trabajo y disciplina, pero vale la pena.
Mentalidad de service worker
Adoptar la mentalidad adecuada mientras piensas en los trabajadores de servicio te ayudará a crear el tuyo con confianza. Una vez que domines estas funciones, podrás crear experiencias increíbles para tus usuarios.
Si quieres comprender todo esto jugando un juego, estás de suerte. Juega a Service Workies, en el que aprenderás a usar el trabajador de servicio para derrotar a las bestias sin conexión.