Mentalidad de service worker

Cómo pensar en los service workers

Los service workers son potentes y realmente vale la pena aprenderlos. Te permiten ofrecer un nivel de experiencia completamente nuevo a tus usuarios. Tu sitio puede cargarse al instante. Puede funcionar sin conexión. Puede instalarse como una app específica de una plataforma y verse como pulida, pero con el alcance y la libertad de la Web.

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

Google Developers y yo colaboramos recientemente en un proyecto, Service Workies, un juego gratuito para comprender los service workers. Mientras la compilaba y trabajaba con los pormenores de los service workers, me encontré con algunos problemas. Lo que más me ayudó fue proponer un puñado de metáforas representaciones. En esta publicación, exploraremos estos modelos mentales y analizaremos nuestros cerebros en los rasgos paradójicos que hacen que los service workers sean tanto complicados como asombrosos.

La misma, pero diferente

Mientras codificas tu service worker, muchas cosas te resultarán familiares. Puedes usar tus nuevas funciones favoritas del lenguaje JavaScript. Debes escuchar eventos de ciclo de vida de la misma manera que con eventos de IU. Administras el flujo de control con promesas como de costumbre.

Pero otro comportamiento del service worker te hace confundirte. especialmente cuando actualizas la página y no ves los cambios de código aplicados.

Una nueva capa

Normalmente, cuando creas un sitio, solo tienes que pensar en 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 tu service worker como una especie de extensión del navegador: una que tu sitio puede instalar en el navegador del usuario. Una vez instalado, el service worker extiende el navegador para tu sitio con una potente capa intermedia. Esta capa de service worker puede interceptar y controlar todas las solicitudes que realiza tu sitio.

La capa de service worker tiene su propio ciclo de vida, independientemente 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 esperarías que se actualice la página para actualizar el código implementado en un servidor. Cada capa tiene sus propias reglas únicas de actualización.

En el juego Service Workies, abarcamos todos los detalles del ciclo de vida de los service workers y tenemos mucha práctica para trabajar con ellos.

Potente, pero limitado

Tener un service worker en tu sitio te brinda beneficios increíbles. Tu sitio puede hacer lo siguiente:

Con todo lo que pueden hacer los service workers, están limitados por su diseño. No pueden realizar ninguna acción síncrona ni en la misma conversación que tu sitio. Eso significa que no se podrá 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 postMessage directo, canales de mensajes uno a uno y canales de transmisión uno a varios.

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

Un service worker activo sigue vivo incluso después de que un usuario abandona tu sitio o cierra la pestaña. El navegador mantiene este service worker disponible para que esté listo la próxima vez que el usuario regrese a tu sitio. Antes de realizar la primera solicitud, el service worker tiene la oportunidad de interceptarlo y tomar el control de la página. Esto es lo que permite que un sitio funcione sin conexión: el service worker puede procesar una versión almacenada en caché de la página, incluso si el usuario no tiene conexión a Internet.

En Service Workies, visualizamos este concepto con Kolohe (un service worker amigable) interceptando y controlando solicitudes.

Detenida

A pesar de que los service workers parecen inmortales, se pueden detener 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: el service worker permanece instalado y activado. Se pone en suspensión. La próxima vez que sea necesario (p.ej., para controlar una solicitud), el navegador la volverá a activar.

waitUntil

Debido a la posibilidad constante de irse a dormir, tu service worker necesita una forma de permitir que el navegador sepa que está haciendo algo importante y no tiene ganas de 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 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 termina de instalarse hasta que se haya creado la caché 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"]);
    })
  );
});

Presta atención al estado global

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

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 por un tiempo, por lo que el navegador lo detiene. La próxima vez que haya una solicitud, se volverá a necesitar el service worker, por lo que el navegador lo activará. Su secuencia de comandos se vuelve a evaluar. Ahora, hasHandledARequest se restableció a false, y favoriteNumber es algo completamente diferente: 0.5907281835659033.

No puedes confiar en el estado almacenado en un service worker. Además, crear instancias de elementos como canales de mensajes puede provocar errores: obtendrás una instancia completamente nueva cada vez que se detenga o se inicie el service worker.

En el Capítulo 3 de Service Workies, visualizamos el service worker detenido mientras pierde color mientras espera a que se active.

visualización de un service worker detenido

Juntos, pero separados

Tu página solo puede controlarse con un service worker a la vez. Sin embargo, puede tener dos service workers instalados a la vez. Cuando realizas un cambio en el código de tu service worker y actualizas la página, no estás editando tu service worker. Los service workers son inmutables. En cambio, estás creando una nueva. Este nuevo service worker (llamado SW2) se instalará, pero aún no se activará. Debe esperar a que finalice el service worker actual (SW1) (cuando el usuario abandona tu sitio).

Intercambiar las cachés de otro service worker

Durante la instalación, SW2 puede establecer los elementos, por lo general, creando y propagando cachés. Pero ten en cuenta que este nuevo service worker tiene acceso a todo a lo que tiene acceso el service worker actual. Si no tienes cuidado, tu nuevo service worker en espera realmente puede arruinarle las cosas a tu service worker 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 los contenidos de una caché que esté utilizando SW1, lo que provocaría que SW1 responda con recursos que la página no esperaba.

Omitir navegación en espera

Un service worker también puede usar el método riesgoso skipWaiting() para controlar la página apenas finalice la instalación. En general, esta no es una buena idea, a menos que intentes reemplazar intencionalmente un service worker con errores. El nuevo service worker podría estar usando recursos actualizados que la página actual no espera, lo que genera errores.

Iniciar limpieza

La forma de evitar que tus service workers se obstruyan entre sí es asegurarse de que usen diferentes cachés. La forma 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, cambiarás el version para que haga lo que necesita con una caché completamente independiente de la del service worker anterior.

visualización de una caché

Finalizar limpieza

Una vez que tu service worker alcanza el estado activated, sabrás que se reemplazó y el service worker anterior será redundante. En este punto, es importante realizar una limpieza después del service worker antiguo. No solo respeta el comportamiento límites de almacenamiento en caché, pero también puede evitar errores no intencionales.

El método caches.match() es un acceso directo 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 almacenada en assets-2. Sin embargo, si no borraste la caché anterior, caches.match('app.js') mostrará la anterior de assets-1 y probablemente dañará tu sitio.

Lo único que se necesita para limpiar los service workers anteriores es borrar la caché que no necesite el nuevo service worker:

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 service workers se obstruyan entre sí lleva un poco de trabajo y disciplina, pero vale la pena.

Mentalidad de un service worker

Adoptar la mentalidad adecuada mientras piensas en los service workers te ayudará a construir el tuyo con confianza. Una vez que te familiarices con ellas, podrás crear experiencias increíbles para tus usuarios.

Si quieres comprender todo esto jugando, estás de suerte. Juega a los Service Workies, donde aprenderás las formas del service worker para matar a las bestias que no están en línea.