Uso de un trabajador de servicio para administrar las solicitudes de red

Un trabajador de servicio es un tipo especial de trabajo web que puede interceptar, modificar y responder a las solicitudes de red mediante la Fetch API. Para almacenar recursos, un trabajador de servicio puede acceder a la Cache API y puede acceder a almacenes de datos asincrónicos del lado cliente, como IndexedDB.

Un trabajo de servicio puede acelerar la PWA almacenando en caché los recursos localmente y también puede hacer que su PWA sea más confiable al permitir que funcione incluso cuando el dispositivo del usuario está sin conexión o tiene una conexión de red intermitente.

Si el PWA tiene un trabajo de servicio, el trabajo de servicio se instala la primera vez que el usuario accede a su PWA. A continuación, el trabajador del servicio se ejecuta en paralelo con la aplicación y puede seguir trabajando incluso cuando la aplicación no se esté ejecutando.

Un trabajador del servicio es responsable de interceptar, modificar y responder a las solicitudes de red. Se puede alertar al trabajador del servicio cuando la aplicación intenta cargar un recurso desde el servidor o cuando la aplicación envía una solicitud para obtener datos del servidor. Cuando esto sucede, el trabajador del servicio puede decidir dejar que la solicitud vaya al servidor o interceptar la solicitud y devolver una respuesta de la memoria caché en su lugar.

Diagrama que muestra el trabajo de servicio entre la aplicación y la red y el almacenamiento en caché

Registro de un trabajador de servicio

De forma similar a otros trabajos web, un trabajador de servicio debe existir en un archivo independiente. El archivo contiene un único trabajo de servicio. Haga referencia a este archivo al registrar el trabajo de servicio, como se muestra en el código siguiente:

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/serviceworker.js");
}

El explorador web que ejecuta el PWA puede proporcionar diferentes niveles de compatibilidad con el trabajo de servicio. Además, es posible que el contexto en el que se ejecuta la PWA no sea seguro. Por lo tanto, se recomienda probar la existencia del objeto antes de navigator.serviceWorker ejecutar cualquier código relacionado con el trabajo del servicio. En el código anterior, un trabajo de servicio se registra mediante el serviceworker.js archivo que se encuentra en la raíz del sitio.

Asegúrese de colocar el archivo de trabajo de servicio en el directorio de nivel más alto que desea que administre el trabajador del servicio. Este directorio se denomina ámbito del trabajo de servicio. En el código anterior, el archivo se almacena en el directorio raíz de la aplicación y el trabajador del servicio administra todas las páginas que están bajo el nombre de dominio de la aplicación.

Si el archivo de trabajo de servicio se almacena en un js directorio, el ámbito del trabajo de servicio se limitaría al js directorio y a los subdirectorios. Como procedimiento recomendado, coloque el archivo de trabajo de servicio en la raíz de la aplicación, a menos que necesite reducir el ámbito del trabajo de servicio.

Interceptar solicitudes

El evento principal que se usa en un trabajo de servicio es el fetch evento . El fetch evento se ejecuta cada vez que el explorador en el que se ejecuta la aplicación intenta acceder al contenido dentro del ámbito del trabajo del servicio.

En el código siguiente se muestra cómo agregar un agente de escucha para el fetch evento:

self.addEventListener("fetch", event => {
  console.log("Fetching", event.request);
});

Dentro del fetch controlador, puede controlar si una solicitud va a la red, extrae de la memoria caché, etc. El enfoque que adopte probablemente variará en función del tipo de recurso que se solicite, la frecuencia con la que se actualiza y otra lógica de negocios exclusiva de la aplicación.

Estos son algunos ejemplos de lo que puede hacer dentro del fetch controlador:

  • Si está disponible, devuelva una respuesta de la memoria caché; De lo contrario, solicite el recurso a través de la red.
  • Capturar un recurso de la red, almacenar en caché una copia y devolver la respuesta.
  • Permitir a los usuarios especificar una preferencia para guardar datos.
  • Proporcione una imagen de marcador de posición para determinadas solicitudes de imagen.
  • Genere una respuesta directamente en el trabajo de servicio.

Ciclo de vida del trabajador del servicio

El ciclo de vida de un trabajador de servicio consta de varios pasos, y cada paso desencadena un evento. Puede agregar agentes de escucha a estos eventos para ejecutar código para realizar una acción. En la lista siguiente se presenta una vista de alto nivel del ciclo de vida y los eventos relacionados de un trabajador del servicio:

  1. Registre el trabajador del servicio.

  2. El explorador descarga el archivo JavaScript, instala el trabajo del servicio y desencadena el install evento. Puede usar el install evento para almacenar en caché previamente cualquier archivo importante y de larga duración (como archivos CSS, archivos JavaScript, imágenes de logotipo o páginas sin conexión) de la aplicación.

    self.addEventListener("install", event => {
      console.log("Install event in progress.");
    });
    
  3. El trabajo de servicio está activado, lo que desencadena el activate evento. Use este evento para limpiar cachés obsoletas.

    self.addEventListener("activate", event => {
      console.log("Activate event in progress.");
    });
    
  4. El trabajador del servicio está listo para ejecutarse cuando se actualiza la página o cuando el usuario va a una página nueva en el sitio. Si desea ejecutar el trabajo de servicio sin esperar, use el self.skipWaiting() método durante el install evento, como se indica a continuación:

    self.addEventListener("install", event => {
      self.skipWaiting();
    });
    
  5. El trabajador del servicio se está ejecutando y puede escuchar fetch eventos.

Recursos anteriores a la caché

Cuando un usuario accede a la aplicación por primera vez, si ha definido un trabajo de servicio, se instala el trabajo de servicio de la aplicación. Use el evento en el install código de trabajo del servicio para detectar cuándo se produce esto y almacenar en caché todos los recursos estáticos que necesita la aplicación. Almacenar en caché los recursos estáticos de la aplicación (como el código HTML, CSS y JavaScript) que necesita la página de inicio permite que la aplicación se ejecute incluso cuando el dispositivo del usuario esté sin conexión.

Para almacenar en caché los recursos de la aplicación, use el objeto global caches y el cache.addAll método , como se muestra a continuación:

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

Tenga en cuenta que después de la instalación inicial, el install evento no se vuelve a ejecutar. Para actualizar el código del trabajador del servicio, consulte Actualización del trabajo del servicio.

Ahora puede usar el fetch evento para devolver los recursos estáticos de la memoria caché, en lugar de cargarlos de nuevo desde la red:

self.addEventListener("fetch", event => {
  async function returnCachedResource() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Find the response that was pre-cached during the `install` event.
    const cachedResponse = await cache.match(event.request.url);

    if (cachedResponse) {
      // Return the resource.
      return cachedResponse;
    } else {
      // The resource wasn't found in the cache, so fetch it from the network.
      const fetchResponse = await fetch(event.request.url);
      // Put the response in cache.
      cache.put(event.request.url, fetchResponse.clone());
      // And return the response.
      return fetchResponse.
    }
  }

  event.respondWith(returnCachedResource());
});

Por motivos de brevedad, el ejemplo de código anterior no controla los casos en los que se produjo un error al obtener la dirección URL de la solicitud de la red.

Uso de una página sin conexión personalizada

Cuando la aplicación usa varias páginas HTML, un escenario común sin conexión es redirigir las solicitudes de navegación de página a una página de error personalizada cuando el dispositivo del usuario está sin conexión:

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
// Note the offline page in this list.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js", "/offline"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

self.addEventListener("fetch", event => {
  async function navigateOrDisplayOfflinePage() {
    try {
      // Try to load the page from the network.
      const networkResponse = await fetch(event.request);
      return networkResponse;
    } catch (error) {
      // The network call failed, the device is offline.
      const cache = await caches.open(CACHE_NAME);
      const cachedResponse = await cache.match("/offline");
      return cachedResponse;
    }
  }

  // Only call event.respondWith() if this is a navigation request
  // for an HTML page.
  if (event.request.mode === 'navigate') {
    event.respondWith(navigateOrDisplayOfflinePage());
  }
});

Actualización del trabajo de servicio

Instalación de una nueva versión de service worker

Si realiza cambios en el código de trabajo del servicio e implementa el nuevo archivo de trabajo de servicio en el servidor web, los dispositivos de los usuarios comenzarán a usar gradualmente el nuevo trabajo de servicio.

Cada vez que un usuario navega a una de las páginas de la aplicación, el explorador que ejecuta la aplicación comprueba si hay disponible una nueva versión del trabajo de servicio en el servidor. El explorador detecta nuevas versiones comparando el contenido entre el trabajo de servicio existente y el nuevo trabajo de servicio. Cuando se detecta un cambio, se instala el nuevo trabajo de servicio (se desencadena su install evento) y, a continuación, el nuevo trabajador del servicio espera a que el trabajo de servicio existente deje de usarse en el dispositivo.

En la práctica, esto significa que puede haber dos trabajadores de servicio ejecutándose al mismo tiempo, pero solo el trabajador del servicio existente (original) intercepta las solicitudes de red de la aplicación. Cuando se cierra la aplicación, el trabajo de servicio existente deja de usarse. La próxima vez que se abra la aplicación, se activará el nuevo trabajo de servicio. El activate evento se desencadena y el nuevo trabajo de servicio comienza a interceptar fetch eventos.

Puede activar con fuerza el nuevo trabajo de servicio en cuanto esté instalado, mediante self.skipWaiting() el uso en el controlador de eventos del trabajador del install servicio.

Para obtener más información sobre cómo se actualiza un trabajador del servicio, consulte Actualización del trabajador del servicio en web.dev.

Actualizar los archivos estáticos almacenados en caché

Cuando se almacenan en caché previamente recursos estáticos como archivos de hoja de estilos CSS, como se describe en Recursos de caché previa, la aplicación solo usa las versiones almacenadas en caché de los archivos y no intenta descargar nuevas versiones.

Para asegurarse de que los usuarios obtienen los cambios más recientes en los recursos estáticos que usa la aplicación, use una convención de nomenclatura de desintegreción de caché y actualice el código de trabajo del servicio.

La desinteción de caché significa que cada archivo estático se denomina según su versión. Esto se puede lograr de varias maneras, pero normalmente implica el uso de una herramienta de compilación que lee el contenido de un archivo y genera un identificador único basado en el contenido. Ese identificador se puede usar para asignar un nombre al archivo estático almacenado en caché.

A continuación, actualice el código de trabajo del servicio para almacenar en caché los nuevos recursos estáticos durante install:

// The name of the new cache your app uses.
const CACHE_NAME = "my-app-cache-v2";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles-124656.css", "app-576391.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache the new static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

// Listen to the `activate` event to clear old caches.
self.addEventListener("activate", event => {
  async function deleteOldCaches() {
    // List all caches by their names.
    const names = await caches.keys();
    await Promise.all(names.map(name => {
      if (name !== CACHE_NAME) {
        // If a cache's name is the current name, delete it.
        return caches.delete(name);
      }
    }));
  }

  event.waitUntil(deleteOldCaches());
});

Compare los CACHE_NAME valores y PRE_CACHED_RESOURCES entre el fragmento de código anterior y el de los recursos de caché previa. Cuando se instala este nuevo trabajo de servicio, se creará una nueva caché y los nuevos recursos estáticos se descargarán y almacenarán en caché. Cuando se activa el trabajo de servicio, se eliminará la memoria caché antigua. En este momento, el usuario tendrá la nueva versión de la aplicación.

A veces, realizar cambios en el trabajo de servicio puede ser complejo. Use una biblioteca como Workbox para simplificar el paso de compilación de recursos estáticos y el código de trabajo del servicio.

Prueba de conexiones de red en la PWA

Resulta útil saber cuándo está disponible una conexión de red, con el fin de sincronizar datos o informar a los usuarios de que el estado de la red ha cambiado.

Use las siguientes opciones para probar la conectividad de red:

La navigator.onLine propiedad es un valor booleano que le permite conocer el estado actual de la red. Si el valor es true, el usuario está en línea; de lo contrario, el usuario está sin conexión.

Para obtener más información, consulte navigator.onLine en MDN.

Eventos en línea y sin conexión

Puede realizar acciones cuando cambie la conectividad de red. Puede escuchar y tomar medidas en respuesta a eventos de red. Los eventos están disponibles en los windowelementos , documenty document.body , como se muestra a continuación:

window.addEventListener("online",  function(){
    console.log("You are online!");
});
window.addEventListener("offline", function(){
    console.log("Network connection lost!");
});

Para obtener más información, consulte Navigator.onLine en MDN.

Otras funcionalidades

La principal responsabilidad de un trabajador del servicio es hacer que la aplicación sea más rápida y confiable en caso de una conexión de red inestable. Normalmente, un trabajador de servicio usa el evento y Cache la fetch API para hacerlo, pero un trabajador del servicio puede usar otras API para escenarios especializados, como:

  • Sincronización en segundo plano de los datos.
  • Sincronización periódica de datos.
  • Descargas de archivos en segundo plano grandes.
  • Control y notificaciones de mensajes push.

Sincronización en segundo plano

Use la API de sincronización en segundo plano para permitir que los usuarios sigan usando la aplicación y realicen acciones incluso cuando el dispositivo del usuario esté sin conexión.

Por ejemplo, una aplicación de correo electrónico puede permitir que sus usuarios redacten y envíen mensajes en cualquier momento. El front-end de la aplicación puede intentar enviar el mensaje de inmediato y, si el dispositivo está sin conexión, el trabajador del servicio puede detectar la solicitud con error y usar la API de sincronización en segundo plano para aplazar la tarea hasta que se conecte.

Para más información, consulte Uso de la API de sincronización en segundo plano para sincronizar datos con el servidor.

Sincronización en segundo plano del período

La API de sincronización en segundo plano periódico permite a los PWA recuperar contenido nuevo periódicamente, en segundo plano, para que los usuarios puedan acceder inmediatamente al contenido cuando vuelvan a abrir la aplicación.

Al usar la API de sincronización en segundo plano periódico, las PPA no tienen que descargar contenido nuevo (por ejemplo, artículos nuevos) mientras el usuario usa la aplicación. La descarga de contenido podría ralentizar la experiencia, por lo que, en su lugar, la aplicación puede recuperar el contenido en un momento más conveniente.

Para obtener más información, consulte Uso de la API de sincronización en segundo plano periódica para obtener contenido nuevo con regularidad.

Descargas de archivos en segundo plano grandes

La API de captura en segundo plano permite a los PWA delegar completamente la descarga de grandes cantidades de datos en el motor del explorador. De este modo, la aplicación y el trabajo de servicio no tienen que ejecutarse mientras la descarga está en curso.

Esta API es útil para aplicaciones que permiten a los usuarios descargar archivos grandes (como música, películas o podcasts) para casos de uso sin conexión. La descarga se delega en el motor del explorador, que sabe cómo controlar una conexión intermitente o incluso una pérdida completa de conectividad.

Para obtener más información, consulte Uso de la API de captura en segundo plano para capturar archivos grandes cuando la aplicación o el trabajo de servicio no se están ejecutando.

Mensajes de inserción

Los mensajes push se pueden enviar a los usuarios sin que tengan que usar la aplicación en ese momento. Un trabajador del servicio puede escuchar mensajes push enviados por el servidor incluso si la aplicación no se está ejecutando y mostrar una notificación en el centro de notificaciones del sistema operativo.

Para más información, consulte Volver a interactuar con los usuarios con mensajes push.

Depuración con DevTools

Con Microsoft Edge DevTools, puede ver si el trabajo de servicio se ha registrado correctamente y ver en qué estado de ciclo de vida se encuentra actualmente el trabajador del servicio. Además, puede depurar el código JavaScript en el trabajo de servicio.

Para más información, consulte Depuración del trabajo de servicio.

Vea también