Mentalidad de service worker

Cómo pensar cuando se piensa en los service worker

Los service workers son potentes y vale la pena aprenderlos. Te permiten ofrecer un nivel de experiencia completamente nuevo a tus usuarios. Tu sitio puede cargarse al instante. Funciona sin conexión. Se puede instalar como una app específica de la plataforma y parece estar casi perfecta, pero con el alcance y la libertad de la Web.

Sin embargo, los service workers no se parecen a nada a lo que la mayoría de los desarrolladores web estamos acostumbrados. Tienen una curva de aprendizaje muy pronunciada y algunos problemas que debes tener en cuenta.

Recientemente, Google Developers y yo colaboramos en un proyecto, los servicios de trabajo, un juego gratuito para comprender a los procesos de trabajo de servicio. Mientras la creaba y trabajé con los pormenores de los trabajadores de servicio, encontré algunos inconvenientes. Lo que más me ayudó a encontrar un puñado de metáforas representativas. En esta publicación, exploraremos estos modelos mentales y conoceremos los rasgos paradójicos que hacen que los service workers sean engañosos y asombrosos.

Igual, pero diferente

Cuando codifiques tu service worker, hay muchos aspectos que te resultarán familiares. Puedes usar tus nuevas funciones favoritas del lenguaje JavaScript. Escuchas los eventos de 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, otro comportamiento de un service worker te confunde. en especial, cuando actualizas la página y no ves los cambios de código aplicados.

Una nueva capa

Normalmente, al crear un sitio, solo hay que considerar dos capas: el cliente y el servidor. El service worker es una capa nueva que se ubica en el medio.

Un service worker actúa como una capa intermedia entre el cliente y el servidor

Piensa en el service worker como una especie de extensión de navegador: una que tu sitio puede instalar en el navegador del usuario. Una vez instalado, el service worker extends el navegador del sitio con una capa central potente. Esta capa de service worker puede interceptar y controlar todas las solicitudes que realiza tu sitio.

La capa del service worker tiene su propio ciclo de vida, independiente de la pestaña del navegador. Una simple actualización de la página no es suficiente para actualizar un service worker, del mismo modo que no se espera que una página actualice el código implementado en un servidor. Cada capa tiene sus propias reglas de actualización únicas.

En el juego Service Workies, abordamos los muchos detalles del ciclo de vida del service worker y te daremos un montón de 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:

Con tanto como pueden hacer los service workers, su diseño los limita. No pueden realizar ninguna acción de forma síncrona ni en la misma conversación que tu sitio. Eso significa que no se puede acceder a lo siguiente:

  • localStorage
  • el DOM
  • la ventana

La buena noticia es que tu página puede comunicarse con su service worker de diferentes maneras, como los postMessage directos, los Canales de mensajes uno a uno y los Canales de transmisión uno a varios.

De larga duración, pero de corta duración

Un service worker activo sigue activo incluso después de que un usuario abandona el sitio o cierra la pestaña. El navegador mantiene este service worker presente para que esté listo la próxima vez que el usuario regrese a tu sitio. Antes de que se realice la primera solicitud, el service worker 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 mostrar una versión almacenada en caché de la página, incluso si el usuario no tiene conexión a Internet.

En los trabajos de servicio, visualizamos este concepto mediante Kolohe (un service worker amigable) que intercepta y maneja las solicitudes.

Detenido

A pesar de que los service workers parecen ser inmortales, pueden detenerse casi en cualquier momento. El navegador no quiere desperdiciar recursos en un service worker que actualmente no está haciendo nada. Detenerse no es lo mismo que cerrarse, ya que el service worker permanece instalado y activado. Se pone en modo de suspensión. La próxima vez que lo necesites (p.ej., para procesar una solicitud), el navegador la volverá a activar.

waitUntil

Debido a la constante posibilidad de irse a dormir, el service worker necesita una forma de avisarle al navegador que está realizando alguna acción importante y que no tiene ganas de tomar una siesta. Aquí es donde event.waitUntil() entra en juego. Este método extiende el ciclo de vida en el que se utiliza, lo que evita que se detenga y pase a la siguiente fase de su ciclo de vida hasta que estemos listos. Esto nos da tiempo para configurar cachés, recuperar recursos de la red, etcétera.

En este ejemplo, se indica al navegador que nuestro service worker no ha terminado de instalarse hasta que se haya creado la caché assets y se haya propagado la imagen de una espada:

self.addEventListener("install", event => {
  event.waitUntil(
    caches.open("assets").then(cache => {
      return cache.addAll(["/weapons/sword/blade.png"]);
    })
  );
});

Tenga cuidado con el estado global

Cuando ocurre este inicio o la detención, se restablece el alcance global del service worker. Por lo tanto, ten cuidado de no usar ningún estado global en tu service worker. De lo contrario, la próxima vez que se active y tenga un estado diferente al que esperabas, te pondrás triste.

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 service worker registrará un número, por ejemplo, 0.13981866382421893. La variable hasHandledARequest también cambia a true. Ahora el service worker permanece inactivo durante un tiempo, y el navegador lo detiene. La próxima vez que haya una solicitud, se necesitará nuevamente el service worker y el navegador lo activará. Su secuencia de comandos se vuelve a evaluar. Ahora, se restableció hasHandledARequest a false, y favoriteNumber es algo completamente diferente: 0.5907281835659033.

No puedes confiar en el estado almacenado en un service worker. Además, la creación de instancias de elementos como los canales de mensajes puede causar errores, ya que obtendrás una instancia nueva cada vez que el service worker se detenga o se inicie.

En el capítulo 3 de Service Workies, visualizamos que el service worker detenido pierde todo el color mientras espera a que lo despierte.

Visualización de un service worker detenido

Todos juntos, pero separados

Solo un service worker a la vez puede controlar la página. Sin embargo, puedes tener dos service worker instalados a la vez. Cuando realizas un cambio en el código de tu service worker y actualizas la página, en realidad no estás editando el service worker. Los service workers son inmutables. sino que harás una nueva. Este nuevo service worker (que llamaremos SW2) se instalará, pero aún no se activará. Debe esperar a que finalice el service worker actual (SW1) (cuando el usuario abandone tu sitio).

Manipulación de las cachés de otro service worker

Durante la instalación, SW2 puede configurarse, generalmente, creando y propagando cachés. Sin embargo, ten en cuenta que este nuevo service worker tiene acceso a todo lo que puede acceder el service worker actual. Si no tienes cuidado, tu nuevo service worker en espera realmente puede arruinar el trabajo de tu actual service worker. 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 SW1 está usando, lo que hará que SW1 responda con elementos que la página no espera.

Omitir omitir en espera

Un service worker también puede usar el método riesgoso skipWaiting() para tomar el control de la página en cuanto termine de instalarse. Por lo general, no es una buena idea, a menos que intentes reemplazar intencionalmente un service worker con errores. Es posible que el nuevo service worker esté usando recursos actualizados que la página actual no espera, lo que genera errores.

Comenzar a limpiar

La forma de evitar que los service workers se dañen entre sí es asegurarse de que usen cachés diferentes. La manera más fácil de lograrlo 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, transmitirás el version para que haga lo que necesite con una caché completamente independiente del service worker anterior.

visualización de una caché

Finalizar limpieza

Cuando el service worker llega al estado activated, sabrás que tomó el control y el service worker anterior será redundante. En este punto, es importante realizar una limpieza después del service worker anterior. No solo respeta los límites de almacenamiento en caché de los usuarios, sino que también puede prevenir errores accidentales.

El método caches.match() es un atajo de uso frecuente 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 está almacenada en assets-2. Sin embargo, si no borraste la caché anterior, caches.match('app.js') devolverá la anterior de assets-1 y es probable que dañe tu sitio.

Una vez que los service worker anteriores realizan una limpieza, solo deberás borrar la caché que el nuevo service worker no necesite:

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 los trabajadores de servicio se dañen entre sí requiere un poco de trabajo y disciplina, pero vale la pena.

Mentalidad de service worker

Adquirir la mentalidad adecuada mientras piensas en service workers te ayudará a desarrollar la tuya con confianza. Una vez que te familiarices con ellas, podrás crear experiencias increíbles para tus usuarios.

Si quieres entender todo esto jugando un juego, estás de suerte. Juega a los trabajos de servicio, en los que aprenderás las formas del service worker para matar a los animales sin conexión.