[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/es/docs/Web/API/Service_Worker_API/Using_Service_Workers [Back]  [Original]

Uso de Service Workers - API web | MDN

Esta pgina ha sido traducida del ingls por la comunidad. Aprende ms y nete a la comunidad de MDN Web Docs.

View in English Always switch to English

Uso de Service Workers

Este artculo proporciona informacin sobre cmo comenzar con los service workers, incluyendo la arquitectura bsica, el registro de un service worker, el proceso de instalacin y activacin de un nuevo service worker, la actualizacin del service worker, el control de cach y las respuestas personalizadas, todo en el contexto de una aplicacin con funcionalidad sin conexin.

In this article

La premisa de los service workers

Un problema predominante que los usuarios web han sufrido durante aos es la prdida de conectividad. La mejor aplicacin web del mundo proporcionar una experiencia de usuario terrible si no se puede descargar. Ha habido varios intentos de crear tecnologas para resolver este problema, y algunos de los problemas se han resuelto. Pero el problema predominante es que no exista un buen mecanismo de control general para el almacenamiento en cach de recursos y las solicitudes de red personalizadas.

Los service workers solucionan estos problemas. Usando un service worker se puede configurar una aplicacin para usar recursos almacenados en cach primero, proporcionando as una experiencia predeterminada incluso sin conexin, antes de obtener ms datos de la red (comnmente conocido como "offline first"). Esto ya est disponible con las aplicaciones nativas, que es una de las principales razones por las que las aplicaciones nativas se eligen a menudo en lugar de las aplicaciones web.

Un service worker funciona como un servidor proxy, permitiendo modificar las solicitudes y respuestas reemplazndolas con elementos de su propia cach.

Configuracin para trabajar con service workers

Los service workers estn habilitados de forma predeterminada en todos los navegadores modernos. Para ejecutar cdigo que use service workers, se necesita servir el cdigo a travs de HTTPS, ya que los service workers estn restringidos a ejecutarse sobre HTTPS por razones de seguridad. Es necesario un servidor que soporte HTTPS. Para alojar experimentos, se puede usar un servicio como GitHub, Netlify, Vercel, etc. Para facilitar el desarrollo local, los navegadores tambin consideran localhost como un origen seguro.

Arquitectura bsica

Con los service workers, generalmente se observan los siguientes pasos para la configuracin bsica:

  1. Se obtiene el cdigo del service worker y se registra usando serviceWorkerContainer.register(). Si tiene xito, el service worker se ejecuta en un ServiceWorkerGlobalScope; esto es bsicamente un tipo especial de contexto worker, que se ejecuta fuera del hilo principal de ejecucin del script, sin acceso al DOM. El service worker ahora est listo para procesar eventos.
  2. Se lleva a cabo la instalacin. Un evento install siempre es el primero enviado a un service worker (esto se puede usar para iniciar el proceso de llenar un IndexedDB y almacenar en cach los recursos del sitio). Durante este paso, la aplicacin se prepara para tener todo disponible para uso sin conexin.
  3. Cuando el controlador de install se completa, el service worker se considera instalado. En este punto, una versin anterior del service worker puede estar activa y controlando pginas abiertas. Como no se desea tener dos versiones diferentes del mismo service worker ejecutndose al mismo tiempo, la nueva versin an no est activa.
  4. Una vez que todas las pginas controladas por la versin anterior del service worker se han cerrado, es seguro retirar la versin anterior, y el service worker recin instalado recibe un evento activate. El uso principal de activate es limpiar los recursos utilizados en versiones anteriores del service worker. El nuevo service worker puede llamar a skipWaiting() para solicitar ser activado inmediatamente sin esperar a que se cierren las pginas abiertas. El nuevo service worker recibir entonces activate inmediatamente y tomar el control de cualquier pgina abierta.
  5. Despus de la activacin, el service worker controlar las pginas, pero solo aquellas que se abrieron despus de que register() tenga xito. En otras palabras, los documentos tendrn que recargarse para ser controlados realmente, porque un documento comienza su vida con o sin un service worker y lo mantiene durante toda su vida. Para anular este comportamiento predeterminado y adoptar las pginas abiertas, un service worker puede llamar a clients.claim().
  6. Cada vez que se obtiene una nueva versin de un service worker, este ciclo se repite y los restos de la versin anterior se limpian durante la activacin de la nueva versin.

diagrama de ciclo de vida [diagrama de ciclo de vida]

Este es un resumen de los eventos de service worker disponibles:

Demostracin

Para demostrar los conceptos bsicos de registro e instalacin de un service worker, se ha creado una demostracin simple llamada simple service worker, que es una galera de imgenes de Star Wars Lego. Utiliza una funcin basada en promesas para leer datos de imagen de un objeto JSON y cargar las imgenes usando fetch(), antes de mostrar las imgenes en lnea en la pgina. Se ha mantenido esttico por ahora. Tambin registra, instala y activa un service worker.

Las palabras Star Wars seguidas de una imagen de una versin Lego del personaje Darth Vader [Las palabras Star Wars seguidas de una imagen de una versin Lego del personaje Darth Vader]

Se puede ver el cdigo fuente en GitHub y el simple service worker ejecutndose en vivo.

Registrar el worker

El primer bloque de cdigo en el archivo JavaScript de la aplicacin, app.js, es el siguiente. Este es el punto de entrada para usar service workers.

js
const registerServiceWorker = async () => {
  if ("serviceWorker" in navigator) {
    try {
      const registration = await navigator.serviceWorker.register("/sw.js", {
        scope: "/",
      });
      if (registration.installing) {
        console.log("Service worker instalndose");
      } else if (registration.waiting) {
        console.log("Service worker instalado");
      } else if (registration.active) {
        console.log("Service worker activo");
      }
    } catch (error) {
      console.error(`El registro fall con ${error}`);
    }
  }
};

// 

registerServiceWorker();
  1. El bloque if realiza una prueba de deteccin de caractersticas para asegurarse de que los service workers sean compatibles antes de intentar registrar uno.
  2. A continuacin, se usa la funcin ServiceWorkerContainer.register() para registrar el service worker para este sitio. El cdigo del service worker es un archivo JavaScript que reside dentro de la aplicacin (ntese que esta es la URL del archivo relativa al origen, no al archivo JS que lo referencia).
  3. El parmetro scope es opcional y se puede usar para especificar el subconjunto del contenido que se desea que el service worker controle. En este caso, se ha especificado '/', lo que significa todo el contenido bajo el origen de la aplicacin. Si se omite, tomar este valor predeterminado de todos modos, pero se ha especificado aqu con fines ilustrativos.

Esto registra un service worker, que se ejecuta en un contexto worker y, por lo tanto, no tiene acceso al DOM.

Un solo service worker puede controlar muchas pginas. Cada vez que se carga una pgina dentro de su alcance, el service worker se instala para esa pgina y opera en ella. Por lo tanto, es necesario tener cuidado con las variables globales en el script del service worker: cada pgina no tiene su propio worker nico.

Nota: Una gran ventaja de los service workers es que si se usa la deteccin de caractersticas como se muestra arriba, los navegadores que no soportan service workers pueden simplemente usar la aplicacin en lnea de la manera normal esperada.

Por qu falla el registro de mi service worker?

Un service worker falla en registrarse por una de las siguientes razones:

  • No se est ejecutando la aplicacin en un contexto seguro (sobre HTTPS).
  • La ruta del archivo del service worker es incorrecta. La ruta debe ser relativa al origen, no al directorio raz de la aplicacin. En el ejemplo, el worker est en https://bncb2v.csb.app/sw.js y la raz de la aplicacin es https://bncb2v.csb.app/, por lo que el service worker debe especificarse como /sw.js.
  • La ruta del service worker apunta a un service worker de un origen diferente al de la aplicacin.
  • El registro del service worker contiene una opcin scope ms amplia de lo permitido por la ruta del worker. El alcance predeterminado para un service worker es el directorio donde se encuentra el worker. En otras palabras, si el script sw.js est ubicado en /js/sw.js, solo puede controlar URLs en (o anidadas dentro de) la ruta /js/ de forma predeterminada. El alcance de un service worker se puede ampliar (o reducir) con el encabezado Service-Worker-Allowed.
  • Hay configuraciones especficas del navegador habilitadas, como bloquear todas las cookies, modo de navegacin privada, eliminacin automtica de cookies al cerrar, etc. Consulte la compatibilidad de navegadores de serviceWorker.register() para ms informacin.

Instalacin y activacin: llenar la cach

Despus de que el service worker se registra, el navegador intentar instalar y luego activar el service worker para la pgina/sitio.

El evento install es el primer evento que se dispara en la instalacin o actualizacin del service worker. Se emite una sola vez, inmediatamente despus de que el registro se completa exitosamente, y se usa generalmente para llenar las capacidades de almacenamiento en cach sin conexin del navegador con los recursos necesarios para ejecutar la aplicacin sin conexin. Para esto, se usa la API de almacenamiento del Service Worker, cache, un objeto global en el service worker que permite almacenar recursos entregados por respuestas, indexados por sus solicitudes. Esta API funciona de manera similar a la cach estndar del navegador, pero es especfica para el dominio. Los contenidos de la cach se mantienen hasta que se limpien.

As es como el service worker maneja el evento install:

js
const addResourcesToCache = async (resources) => {
  const cache = await caches.open("v1");
  await cache.addAll(resources);
};

self.addEventListener("install", (event) => {
  event.waitUntil(
    addResourcesToCache([
      "/",
      "/index.html",
      "/style.css",
      "/app.js",
      "/image-list.js",
      "/star-wars-logo.jpg",
      "/gallery/bountyHunters.jpg",
      "/gallery/myLittleVader.jpg",
      "/gallery/snowTroopers.jpg",
    ]),
  );
});
  1. Se agrega un detector de eventos install al service worker (por lo tanto self), y luego se encadena un mtodo ExtendableEvent.waitUntil() al evento, esto asegura que el service worker no se instalar hasta que el cdigo dentro de waitUntil() se haya ejecutado exitosamente.
  2. Dentro de addResourcesToCache() se usa el mtodo caches.open() para crear una nueva cach llamada v1, que ser la versin 1 de la cach de recursos del sitio. Luego se llama a la funcin addAll() en la cach creada, que toma como parmetro un arreglo de URLs relativas al location del worker para todos los recursos que se desea almacenar en cach.
  3. Si la promesa se rechaza, la instalacin falla y el worker no har nada. Esto est bien, ya que se puede corregir el cdigo e intentar de nuevo la prxima vez que ocurra el registro.
  4. Despus de una instalacin exitosa, el service worker se activa. Esto no tiene mucho uso distinto la primera vez que se instala/activa el service worker, pero significa ms cuando se actualiza el service worker (ver la seccin Actualizar el service worker ms adelante).

Nota: La API Web Storage (localStorage) funciona de manera similar a la cach del service worker, pero es sncrona, por lo que no est permitida en service workers.

Nota: IndexedDB se puede usar dentro de un service worker para almacenamiento de datos si se requiere.

Respuestas personalizadas a solicitudes

Ahora que los recursos del sitio estn almacenados en cach, se necesita indicar a los service workers que hagan algo con el contenido almacenado en cach. Esto se hace con el evento fetch.

  1. Un evento fetch se dispara cada vez que se obtiene cualquier recurso controlado por un service worker, lo que incluye los documentos dentro del alcance especificado y cualquier recurso referenciado en esos documentos (por ejemplo, si index.html hace una solicitud de origen cruzado para incrustar una imagen, eso tambin pasa por su service worker).

  2. Se puede adjuntar un detector de eventos fetch al service worker, luego llamar al mtodo respondWith() en el evento para interceptar las respuestas HTTP y actualizarlas con contenido propio.

    js
    self.addEventListener("fetch", (event) => {
      event.respondWith(/* el contenido personalizado va aqu */);
    });
    
  3. Se podra empezar respondiendo con el recurso cuya URL coincida con la de la solicitud de red, en cada caso:

    js
    self.addEventListener("fetch", (event) => {
      event.respondWith(caches.match(event.request));
    });
    

    caches.match(event.request) permite hacer coincidir cada recurso solicitado de la red con el recurso equivalente disponible en la cach, si hay uno coincidente disponible. La coincidencia se realiza a travs de URL y varios encabezados, al igual que con las solicitudes HTTP normales.

Diagrama de evento fetch [Diagrama de evento fetch]

Recuperar solicitudes fallidas

caches.match(event.request) es excelente cuando hay una coincidencia en la cach del service worker, pero qu pasa con los casos en los que no hay coincidencia? Si no se proporciona ningn tipo de manejo de fallos, la promesa se resolvera con undefined y no se obtendra nada.

Despus de probar la respuesta de la cach, se puede recurrir a una solicitud de red regular:

js
const cacheFirst = async (request) => {
  const responseFromCache = await caches.match(request);
  if (responseFromCache) {
    return responseFromCache;
  }
  return fetch(request);
};

self.addEventListener("fetch", (event) => {
  event.respondWith(cacheFirst(event.request));
});

Si los recursos no estn en la cach, se solicitan de la red.

Usando una estrategia ms elaborada, se podra no solo solicitar el recurso de la red, sino tambin guardarlo en la cach para que las solicitudes posteriores de ese recurso tambin se puedan recuperar sin conexin. Esto significara que si se aadieran imgenes adicionales a la galera de Star Wars, la aplicacin podra capturarlas automticamente y almacenarlas en cach. El siguiente fragmento implementa tal estrategia:

js
const putInCache = async (request, response) => {
  const cache = await caches.open("v1");
  await cache.put(request, response);
};

const cacheFirst = async (request, event) => {
  const responseFromCache = await caches.match(request);
  if (responseFromCache) {
    return responseFromCache;
  }
  const responseFromNetwork = await fetch(request);
  event.waitUntil(putInCache(request, responseFromNetwork.clone()));
  return responseFromNetwork;
};

self.addEventListener("fetch", (event) => {
  event.respondWith(cacheFirst(event.request, event));
});

Si la URL de la solicitud no est disponible en la cach, se solicita el recurso de la red con await fetch(request). Despus de eso, se coloca un clon de la respuesta en la cach. La funcin putInCache() usa caches.open('v1') y cache.put() para agregar el recurso a la cach. La respuesta original se devuelve al navegador para entregarse a la pgina que la solicit.

Clonar la respuesta es necesario porque los flujos de solicitud y respuesta solo se pueden leer una vez. Para devolver la respuesta al navegador y ponerla en la cach, es necesario clonarla. As el original se devuelve al navegador y el clon se enva a la cach. Cada uno se lee una vez.

Lo que puede parecer un poco extrao es que la promesa devuelta por putInCache() no se espera. La razn es que no se desea esperar hasta que el clon de respuesta se haya agregado a la cach antes de devolver una respuesta. Sin embargo, es necesario llamar a event.waitUntil() con la promesa, para asegurar que el service worker no se detenga antes de que la cach se haya llenado.

El nico problema ahora es que si la solicitud no coincide con nada en la cach y la red no est disponible, la solicitud seguir fallando. Se puede proporcionar un respaldo predeterminado para que, pase lo que pase, el usuario al menos obtenga algo:

js
const putInCache = async (request, response) => {
  const cache = await caches.open("v1");
  await cache.put(request, response);
};

const cacheFirst = async ({ request, fallbackUrl, event }) => {
  // Primero intentar obtener el recurso de la cach
  const responseFromCache = await caches.match(request);
  if (responseFromCache) {
    return responseFromCache;
  }

  // Luego intentar obtener el recurso de la red
  try {
    const responseFromNetwork = await fetch(request);
    // la respuesta solo se puede usar una vez
    // se necesita guardar el clon para poner una copia en cach
    // y servir la segunda
    event.waitUntil(putInCache(request, responseFromNetwork.clone()));
    return responseFromNetwork;
  } catch (error) {
    const fallbackResponse = await caches.match(fallbackUrl);
    if (fallbackResponse) {
      return fallbackResponse;
    }
    // cuando incluso la respuesta alternativa no est disponible,
    // no hay nada que se pueda hacer, pero siempre se debe
    // devolver un objeto Response
    return new Response("Ocurri un error de red", {
      status: 408,
      headers: { "Content-Type": "text/plain" },
    });
  }
};

self.addEventListener("fetch", (event) => {
  event.respondWith(
    cacheFirst({
      request: event.request,
      fallbackUrl: "/gallery/myLittleVader.jpg",
      event,
    }),
  );
});

Se ha optado por esta imagen alternativa porque las nicas actualizaciones que probablemente fallarn son las imgenes nuevas, ya que todo lo dems depende de la instalacin en el detector de eventos install visto anteriormente.

Precarga de navegacin del service worker

Si est habilitada, la funcin de precarga de navegacin comienza a descargar recursos tan pronto como se realiza la solicitud de fetch, y en paralelo con la activacin del service worker. Esto asegura que la descarga comience inmediatamente al navegar a una pgina, en lugar de tener que esperar hasta que el service worker est activado. Ese retraso ocurre relativamente rara vez, pero es inevitable cuando sucede y puede ser significativo.

Primero, la funcin debe habilitarse durante la activacin del service worker, usando registration.navigationPreload.enable():

js
self.addEventListener("activate", (event) => {
  event.waitUntil(self.registration?.navigationPreload.enable());
});

Luego se usa event.preloadResponse para esperar a que el recurso precargado termine de descargarse en el controlador de eventos fetch.

Continuando con el ejemplo de las secciones anteriores, se inserta el cdigo para esperar el recurso precargado despus de la verificacin de cach, y antes de obtenerlo de la red si eso no tiene xito.

El nuevo proceso es:

  1. Verificar la cach.
  2. Esperar event.preloadResponse, que se pasa como preloadResponsePromise a la funcin cacheFirst(). Almacenar en cach el resultado si se obtiene.
  3. Si ninguno de estos est definido, se recurre a la red.
js
const addResourcesToCache = async (resources) => {
  const cache = await caches.open("v1");
  await cache.addAll(resources);
};

const putInCache = async (request, response) => {
  const cache = await caches.open("v1");
  await cache.put(request, response);
};

const cacheFirst = async ({
  request,
  preloadResponsePromise,
  fallbackUrl,
  event,
}) => {
  // Primero intentar obtener el recurso de la cach
  const responseFromCache = await caches.match(request);
  if (responseFromCache) {
    return responseFromCache;
  }

  // Luego intentar usar (y almacenar en cach) la respuesta precargada, si est disponible
  const preloadResponse = await preloadResponsePromise;
  if (preloadResponse) {
    console.info("using preload response", preloadResponse);
    event.waitUntil(putInCache(request, preloadResponse.clone()));
    return preloadResponse;
  }

  // Luego intentar obtener el recurso de la red
  try {
    const responseFromNetwork = await fetch(request);
    // la respuesta solo se puede usar una vez
    // se necesita guardar el clon para poner una copia en cach
    // y servir la segunda
    event.waitUntil(putInCache(request, responseFromNetwork.clone()));
    return responseFromNetwork;
  } catch (error) {
    const fallbackResponse = await caches.match(fallbackUrl);
    if (fallbackResponse) {
      return fallbackResponse;
    }
    // cuando incluso la respuesta alternativa no est disponible,
    // no hay nada que se pueda hacer, pero siempre se debe
    // devolver un objeto Response
    return new Response("Ocurri un error de red", {
      status: 408,
      headers: { "Content-Type": "text/plain" },
    });
  }
};

// Habilitar precarga de navegacin
const enableNavigationPreload = async () => {
  if (self.registration.navigationPreload) {
    await self.registration.navigationPreload.enable();
  }
};

self.addEventListener("activate", (event) => {
  event.waitUntil(enableNavigationPreload());
});

self.addEventListener("install", (event) => {
  event.waitUntil(
    addResourcesToCache([
      "/",
      "/index.html",
      "/style.css",
      "/app.js",
      "/image-list.js",
      "/star-wars-logo.jpg",
      "/gallery/bountyHunters.jpg",
      "/gallery/myLittleVader.jpg",
      "/gallery/snowTroopers.jpg",
    ]),
  );
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    cacheFirst({
      request: event.request,
      preloadResponsePromise: event.preloadResponse,
      fallbackUrl: "/gallery/myLittleVader.jpg",
      event,
    }),
  );
});

Ntese que en este ejemplo se descargan y almacenan en cach los mismos datos para el recurso, ya sea que se descargue "normalmente" o se precargue. En su lugar, se puede optar por descargar y almacenar en cach un recurso diferente en la precarga. Para ms informacin, consulte NavigationPreloadManager > Respuestas personalizadas.

Actualizar el service worker

Si el service worker se instal previamente, pero luego hay una nueva versin del worker disponible al refrescar o cargar la pgina, la nueva versin se instala en segundo plano, pero an no se activa. Solo se activa cuando ya no hay pginas cargadas que an usen el service worker antiguo. Tan pronto como no queden ms pginas cargadas de este tipo, el nuevo service worker se activa.

Nota: Es posible evitar esto usando Clients.claim().

Se querr actualizar el detector de eventos install en el nuevo service worker a algo como esto (ntese el nuevo nmero de versin):

js
const addResourcesToCache = async (resources) => {
  const cache = await caches.open("v2");
  await cache.addAll(resources);
};

self.addEventListener("install", (event) => {
  event.waitUntil(
    addResourcesToCache([
      "/",
      "/index.html",
      "/style.css",
      "/app.js",
      "/image-list.js",

      // 

      // incluir otros nuevos recursos para la nueva versin
    ]),
  );
});

Mientras el service worker se est instalando, la versin anterior sigue siendo responsable de los fetches. La nueva versin se est instalando en segundo plano. Se est llamando a la nueva cach v2, por lo que la cach anterior v1 no se ve afectada.

Cuando ninguna pgina est usando la versin anterior, el nuevo worker se activa y se vuelve responsable de los fetches.

Eliminar cachs antiguas

Como se vio en la seccin anterior, cuando se actualiza un service worker a una nueva versin, se crea una nueva cach en el controlador de eventos install. Mientras haya pginas abiertas controladas por la versin anterior del worker, es necesario mantener ambas cachs, porque la versin anterior necesita su versin de la cach. Se puede usar el evento activate para eliminar datos de las cachs anteriores.

Las promesas pasadas a waitUntil() bloquearn otros eventos hasta completarse, por lo que se puede estar seguro de que la operacin de limpieza se habr completado para cuando se reciba el primer evento fetch en el nuevo service worker.

js
const deleteCache = async (key) => {
  await caches.delete(key);
};

const deleteOldCaches = async () => {
  const cacheKeepList = ["v2"];
  const keyList = await caches.keys();
  const cachesToDelete = keyList.filter((key) => !cacheKeepList.includes(key));
  await Promise.all(cachesToDelete.map(deleteCache));
};

self.addEventListener("activate", (event) => {
  event.waitUntil(deleteOldCaches());
});

Herramientas de desarrollo

Vase tambin


Web Proxy Viewer  |  New URL  |  Original Page