Esta página ha sido traducida del inglés por la comunidad.Aprende más y únete a la comunidad de MDN Web Docs.
Usar Service Workers
Este artículo brinda información sobre cómo comenzar con elservice worker, incluida la arquitectura básica, el registro de unservice worker, el proceso de instalación y activación de un nuevoservice worker, la actualización de tuservice worker, el control de caché y las respuestas personalizadas, todo en el contexto de una aplicación simple, con funcionalidad fuera de línea.
In this article
La premisa del service worker
Un problema primordial del que los usuarios de la web han adolecido durante años es la pérdida de conectividad. La mejor aplicación web del mundo proporcionará una experiencia de usuario terrible si no la puedes descargar. Ha habido varios intentos de crear tecnologías para resolver este problema, y algunos de los problemas se han resuelto. Pero el problema primordial es que todavía no existe un buen mecanismo de control general para el almacenamiento en caché de activos y las solicitudes de red personalizadas.
El intento anterior,AppCache, parecía ser una buena idea porque te permitía especificar activos para almacenar en caché con mucha facilidad. Sin embargo, hizo muchas suposiciones sobre lo que estabas tratando de hacer y luego se rompió horriblemente cuando tu aplicación no siguió exactamente esas suposiciones. Lee el documento de Jake Archibald (desafortunadamente mal titulado pero bien escrito)Application Cache is a Douchebag para obtener más detalles.
Nota:A partir de Firefox 84, se eliminóAppCache (Error 1619673 en Firefox). También se haeliminado de Chromium 95 y está obsoleto en Safari.
Elservice worker finalmente debería solucionar estos problemas. La sintaxis delservice worker es más compleja que la deAppCache, pero la compensación es que puedes usar JavaScript para controlar su comportamiento implícito enAppCache con un buen grado de fina granularidad, lo que te permite manejar este problema y muchos más. Al usar unservice worker, puedes configurar fácilmente una aplicación para usar activos almacenados en caché primero, proporcionando así una experiencia predeterminada incluso cuando estás desconectado, antes de obtener más datos de la red (comúnmente conocido comoPrimero sin conexión). Esto ya está disponible con las aplicaciones nativas, que es una de las principales razones por las que las aplicaciones nativas a menudo se eligen en lugar de las aplicaciones web.
Configuración para jugar con el service worker
En estos días, elservice worker está habilitado de forma predeterminada en todos los navegadores modernos. Para ejecutar código con elservice worker, deberás entregar tu código a través de HTTPS: elservice worker, por razones de seguridad, está restringido a ejecutarse a través de HTTPS. Por lo tanto, GitHub es un buen lugar para alojar experimentos, ya que admite HTTPS. Para facilitar el desarrollo local, los navegadores también consideranlocalhost como un origen seguro.
Arquitectura básica
Con elservice worker, generalmente se observan los siguientes pasos para la configuración básica:
- La URL delservice worker se obtiene y registra a través de
serviceWorkerContainer.register(). - Si tiene éxito, elservice worker se ejecuta en
ServiceWorkerGlobalScope; esto es básicamente un tipo especial de contexto de trabajo, que se ejecuta fuera del hilo principal de ejecución del script, sin acceso al DOM. - Elservice worker ahora está listo para procesar eventos.
- Se intenta la instalación delworker cuando se accede posteriormente a las páginas controladas por elservice worker. Un evento de instalación siempre es el primero que se envía a unservice worker (esto se puede usar para iniciar el proceso de completar una IndexedDB «base de datos indexada» y almacenar en caché los activos del sitio). Este es realmente el mismo tipo de procedimiento que instalar una aplicación nativa o Firefox OS: hace que todo esté disponible para usar sin conexión.
- Cuando se completa el controlador
oninstall, se considera que elservice worker está instalado. - Lo siguiente es la activación. Cuando se instala elservice worker, recibe un evento de activación. El uso principal de
onactivatees para la limpieza de los recursos utilizados en versiones anteriores de un script delservice worker. - Elservice worker ahora controlará las páginas, pero solo aquellas que se abran después de que
register()tenga éxito. En otras palabras, los documentos se deberán volver a cargar para controlarlos realmente, porque un documento comienza con o sin unservice worker y lo mantiene durante toda su vida.

El siguiente gráfico muestra un resumen de los eventos deservice worker disponibles:

Demostración del service worker
Para demostrar los conceptos básicos de registro e instalación de unservice worker, hemos creado una demostración simple llamadaservice worker simple, que es una simple galería de imágenes de Star Wars Lego. Utiliza una función impulsada por promesas para leer datos de imagen de un objeto JSON y cargar las imágenes usando Ajax, antes de mostrar las imágenes en una línea hacia abajo en la página. Hemos mantenido las cosas estáticas y simples por ahora. También registra, instala y activa unservice worker, y cuando los navegadores admiten más especificaciones, almacenará en caché todos los archivos necesarios para que funcione sin conexión.

Puedes ver elcódigo fuente en GitHub y elSencilloservice worker ejecutándose en vivo.
Registra a tu worker
El primer bloque de código en el archivo JavaScript de nuestra aplicación,app.js, es el siguiente. Este es nuestro punto de entrada en el uso delservice worker.
const registerServiceWorker = async () => { if ("serviceWorker" in navigator) { try { const registration = await navigator.serviceWorker.register("/sw.js", { scope: "/", }); if (registration.installing) { console.log("Instalando el Service worker"); } else if (registration.waiting) { console.log("Service worker instalado"); } else if (registration.active) { console.log("Service worker activo"); } } catch (error) { console.error(`Falló el registro con el ${error}`); } }};// …registerServiceWorker();- El bloque if realiza una prueba de detección de características para asegurarse de que elservice worker sea compatible antes de intentar registrar uno.
- A continuación, usamos la función
ServiceWorkerContainer.register()para registrar elservice worker para este sitio, que solo es un archivo JavaScript que reside dentro de nuestra aplicación (ten en cuenta que esta es la URL del archivo relativa al origen , no el archivo JS que hace referencia a él). - El parámetro
scopees opcional y se puede usar para especificar el subconjunto de tu contenido que deseas controle elservice worker. En este caso, hemos especificado'/', lo cual significa todo el contenido bajo el origen de la aplicación. Si lo omites, tendrá este valor predeterminado de todos modos, pero lo especificamos aquí con fines ilustrativos.
Esto registra unservice worker, que se ejecuta en un contexto de trabajador y, por lo tanto, no tiene acceso al DOM. Luego ejecuta el código en elservice worker fuera de tus páginas normales para controlar su carga.
Un soloservice worker puede controlar muchas páginas. Cada vez que se carga una página dentro de su alcance, elservice worker se instala en esa página y opera en ella. Por lo tanto, ten en cuenta que debes tener cuidado con las variables globales en el script delservice worker: cada página no tiene su propio trabajador único.
Nota:Tuservice worker funciona como un servidor proxy, lo que te permite modificar solicitudes y respuestas, reemplazarlas con elementos de su propio caché y más.
Nota:Una gran cosa acerca delservice worker es que si usas la detección de funciones como se muestra arriba, los navegadores que no son compatibles con losservice workers pueden usar tu aplicación en línea de la manera normal esperada. Además, si usasAppCache ySW en una página, los navegadores que no admitenSW pero síAppCache lo usarán, y los navegadores que admiten ambos ignoraránAppCache y dejarán queSW tome el control.
¿Por qué mi service worker no se registra?
Esto se podría deber a las siguientes razones:
- No estás ejecutando tu aplicación a través de HTTPS.
- La ruta a tu archivo delservice worker no está escrita correctamente — se debe escribir en relación con el origen, no con el directorio raíz de tu aplicación. En nuestro ejemplo, el trabajador está en
https://bncb2v.csb.app/sw.jsy la raíz de la aplicación eshttps://bncb2v.csb.app/. Pero la ruta se debe escribir como/sw.js. - Tampoco está permitido apuntar a unservice worker de un origen diferente al de tu aplicación.

También ten en cuenta:
- Elservice worker solo capturará las solicitudes de los clientes bajo el alcance delservice worker.
- El alcance máximo para unservice worker es la ubicación del trabajador.
- Si tuservice worker está activo en un cliente al que se atiende con el encabezado
Service-Worker-Allowed, puedes especificar una lista de alcances máximos para ese trabajador. - En Firefox, las APIs deService Worker están ocultas y no se pueden usar cuando el usuario está enmodo de navegación privada.
Instalar y activar: llena tu caché
Después de que tuservice worker esté registrado, el navegador intentará instalar y luego activar elservice worker para tu página/sitio.
El eventoinstall se activa cuando una instalación se completa con éxito. El eventoinstall generalmente se usa para llenar las capacidades de almacenamiento en caché sin conexión de tu navegador con los activos que necesita para ejecutar tu aplicación sin conexión. Para hacer esto, usamos la API de almacenamiento deService Worker:cache — un objeto global enService Worker que nos permite almacenar los activos entregados por las respuestas y con clave de sus solicitudes. Esta API funciona de manera similar a la memoria caché estándar del navegador, pero es específica para tu dominio. Persiste hasta que le dices que no lo haga — nuevamente, tienes el control total.
Así es como nuestroservice worker maneja el eventoinstall:
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", ]), );});- Aquí agregamos un detector de eventos
installalservice worker (por lo tanto,self), y luego encadenamos un métodoExtendableEvent.waitUntil()al evento; esto garantiza que elservice worker no se instale hasta que el código dentro dewaitUntil()haya ocurrido con éxito. - Dentro de
addResourcesToCacheusamos el métodocaches.open()para crear un nuevo caché llamadov1, que será la versión 1 de nuestro caché de recursos del sitio. Luego llamamos a una función que llama aaddAll()en el caché creado, que para su parámetro toma un arreglo de URLs relativas al origen de todos los recursos que deseas almacenar en caché. - Si se rechaza la promesa, la instalación falla y el trabajador no hará nada. Esto está bien, ya que puedes corregir tu código y luego intentarlo de nuevo la próxima vez que se registre.
- Después de una instalación exitosa, elservice worker se activa. Esto no tiene mucho de un uso distinto la primera vez que se instala/activa tuservice worker, pero significa más cuando se actualiza elservice worker (consulta la secciónActualizar tuservice worker más adelante).
Nota:localStorage funciona de manera similar a la memoria caché delservice worker, pero es síncrono, por lo que no está permitido en elservice worker.
Nota:IndexedDB se puede usar dentro de unservice worker para el almacenamiento de datos si lo requieres.
Respuestas personalizadas a solicitudes
Ahora que tienes los activos de tu sitio almacenados en caché, debes decir alservice worker que haga algo con el contenido almacenado en caché. Esto se hace fácilmente con el eventofetch.

Un eventofetch se activa cada vez que se recupera cualquier recurso controlado por unservice worker, lo que incluye los documentos dentro del alcance especificado y cualquier recurso al que se haga referencia en esos documentos (por ejemplo, siindex.html hace una solicitud de origen cruzado para incrustar una imagen, que todavía pasa por suservice worker).
Puedes adjuntar un detector de eventosfetch alservice worker, luego llamar al métodorespondWith() en el evento para capturar nuestras respuestas HTTP y actualizarlas con tu propia magia.
self.addEventListener("fetch", (event) => { event .respondWith // la magia va aquí ();});Podríamos empezar respondiendo con el recurso cuya URL coincida con la de la solicitud de red, en cada caso:
self.addEventListener("fetch", (event) => { event.respondWith(caches.match(event.request));});caches.match(event.request) nos permite hacer coincidir cada recurso solicitado de la red con el recurso equivalente disponible en caché, si hay uno coincidente disponible. La coincidencia se realiza a través de URL y varios encabezados, al igual que con las solicitudes HTTP normales.
Veamos algunas otras opciones que tenemos al definir nuestra magia (consulta nuestradocumentación de la API Fetch para obtener más información sobre los objetosRequest yResponse.)
El constructor
Response()te permite crear una respuesta personalizada. En este caso, solo estamos devolviendo una cadena de texto simple:jsnew Response("¡Hola desde tu amigable vecindario del service worker!");Esta
Responsemás compleja a continuación muestra que, opcionalmente, puedes pasar un conjunto de encabezados con tu respuesta, emulando los encabezados de respuesta HTTP estándar. Aquí solo le estamos diciendo al navegador cuál es el tipo de contenido de nuestra respuesta sintética:jsnew Response( "<p>¡Hola desde tu amigable vecindario del service worker!</p>", { headers: { "Content-Type": "text/html" }, },);Si no se encontró una coincidencia en caché, le puedes decir al navegador que
fetch()la solicitud de red predeterminada para ese recurso, para obtener el nuevo recurso de la red si está disponible:jsfetch(event.request);Si no se encontró una coincidencia en caché y la red no está disponible, puedes hacer coincidir la solicitud con algún tipo de página de respaldo predeterminada como respuesta usando
match(), como esta:jscaches.match("./fallback.html");Puedes recuperar mucha información sobre cada solicitud llamando a los parámetros del objeto
Requestdevuelto porFetchEvent:jsevent.request.url;event.request.method;event.request.headers;event.request.body;
Recuperar solicitudes fallidas
Entoncescaches.match(event.request) es excelente cuando hay una coincidencia en caché delservice worker, pero ¿qué pasa con los casos en los que no hay una coincidencia? Si no proporcionamos ningún tipo de manejo de fallas, nuestra promesa se resolvería conundefined y no tendríamos nada devuelto.
Afortunadamente, la estructura basada en promesas delservice worker hace que sea trivial brindar más opciones hacia el éxito. Podríamos hacer esto:
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 están en la memoria caché, se solicitan desde la red.
Si fuéramos realmente inteligentes, no solo solicitaríamos el recurso de la red; ¡también lo guardaríamos en caché para que las solicitudes posteriores de ese recurso también se puedan recuperar sin conexión! Esto significaría que si se agregaran imágenes adicionales a la galería de Star Wars, nuestra aplicación podría capturarlas automáticamente y almacenarlas en caché. Lo siguiente haría el truco:
const putInCache = async (request, response) => { const cache = await caches.open("v1"); await cache.put(request, response);};const cacheFirst = async (request) => { const responseFromCache = await caches.match(request); if (responseFromCache) { return responseFromCache; } const responseFromNetwork = await fetch(request); putInCache(request, responseFromNetwork.clone()); return responseFromNetwork;};self.addEventListener("fetch", (event) => { event.respondWith(cacheFirst(event.request));});Si la URL de la solicitud no está disponible en la memoria caché, solicitamos el recurso de la solicitud de red conawait fetch(request). Después de eso, colocamos en caché un clon de la respuesta. La funciónputInCache usacaches.open('v1') ycache.put() para agregar el recurso a la caché. La respuesta original se devuelve al navegador para que se proporcione a la página que la llamó.
La clonación de la respuesta es necesaria porque los flujos de solicitud y respuesta solo se pueden leer una vez. Para devolver la respuesta al navegador y ponerla en caché la tenemos que clonar. Entonces, el original se devuelve al navegador y el clon se envía a caché. Cada uno se lee una vez.
Lo que puede parecer un poco extraño es que no se espera la promesa devuelta porputInCache. Pero la razón es que no queremos esperar hasta que el clon de respuesta se haya agregado a la caché antes de devolver una respuesta.
El único problema que tenemos ahora es que si la solicitud no coincide con nada en caché y la red no está disponible, nuestra solicitud seguirá fallando. Proporcionemos un respaldo predeterminado para que, pase lo que pase, el usuario al menos obtenga algo:
const putInCache = async (request, response) => { const cache = await caches.open("v1"); await cache.put(request, response);};const cacheFirst = async ({ request, preloadResponsePromise, fallbackUrl }) => { // Primero intenta obtener el recurso desde caché const responseFromCache = await caches.match(request); if (responseFromCache) { return responseFromCache; } // A continuación, intenta obtener el recurso desde la red try { const responseFromNetwork = await fetch(request); // la respuesta solo se puede usar una vez // necesitamos guardar el clon para poner una copia en caché // y servir el segundo 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 podamos hacer, pero siempre debemos // 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", }), );});Hemos optado por esta imagen alternativa porque las únicas actualizaciones que probablemente fallarán son las imágenes nuevas, ya que todo lo demás depende de la instalación en el detector de eventosinstall que vimos anteriormente.
Precarga de navegación del service worker
Si está habilitada, la función deprecarga de navegación comienza a descargar recursos tan pronto como se realiza la solicitud de recuperación y en paralelo con el inicio delservice worker.Esto garantiza que la descarga comience de inmediato al navegar a una página, en lugar de tener que esperar hasta que se inicie elservice worker.Ese retraso ocurre en muy raras ocasiones, pero es inevitable cuando ocurre y puede ser significativo.
Primero, la función debe estar habilitada durante la activación delservice worker, usandoregistration.navigationPreload.enable():
const enableNavigationPreload = async () => { if (self.registration.navigationPreload) { // ¡Habilitar precargas de navegación! await self.registration.navigationPreload.enable(); }};self.addEventListener("activate", (event) => { event.waitUntil(enableNavigationPreload());});Luego usaevent.preloadResponse para esperar a que el recurso precargado se termine de descargar en el controlador de eventosfetch.
Continuando con el ejemplo de las secciones anteriores, insertamos el código para esperar el recurso precargado después de la verificación de la caché y antes de recuperarlo de la red si eso no tiene éxito.
El nuevo proceso es:
- Comprobar la caché
- Esperar en
event.preloadResponse, que se pasa comopreloadResponsePromisea la funcióncacheFirst.Guardar en caché el resultado si regresa. - Si ninguno de estos está definido, vamos a la red.
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 }) => { // Primero intenta obtener el recurso desde caché const responseFromCache = await caches.match(request); if (responseFromCache) { return responseFromCache; } // A continuación, intenta usar (y almacenar en caché) la respuesta precargada, si está allí const preloadResponse = await preloadResponsePromise; if (preloadResponse) { console.info("using preload response", preloadResponse); putInCache(request, preloadResponse.clone()); return preloadResponse; } // A continuación, intenta obtener el recurso desde la red try { const responseFromNetwork = await fetch(request); // la respuesta solo se puede usar una vez // necesitamos guardar el clon para poner una copia en caché // y servir el segundo 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 podamos hacer, pero siempre debemos // devolver un objeto Response return new Response("Ocurrió un error de red", { status: 408, headers: { "Content-Type": "text/plain" }, }); }};// Habilita la precarga de navegaciónconst enableNavigationPreload = async () => { if (self.registration.navigationPreload) { // ¡Habilitar precargas de navegación! 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", }), );});Ten en cuenta que en este ejemplo descargamos y almacenamos en caché los mismos datos para el recurso, ya sea que se descargue "normalmente" o se precargue.En su lugar, puedes optar por descargar y almacenar en caché un recurso diferente en la precarga.Para obtener más información, consultaNavigationPreloadManager > Respuestas personalizadas.
Actualizar tu service worker
Si tuservice worker se instaló anteriormente, pero luego está disponible una nueva versión del trabajador al actualizar o cargar la página, la nueva versión se instala en segundo plano, pero aún no está activada. Solo se activa cuando ya no hay páginas cargadas que todavía estén usando el antiguoservice worker. Tan pronto como no queden más páginas cargadas, se activa el nuevoservice worker.
Querrás actualizar tu escucha de eventosinstall en el nuevoservice worker a algo como esto (observa el nuevo número de versión):
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 versión… ]), );});Mientras esto sucede, la versión anterior sigue siendo responsable de las recuperaciones. La nueva versión se está instalando en segundo plano. Estamos llamando al nuevo cachév2, por lo que el caché anteriorv1 no se ve afectado.
Cuando ninguna página está usando la versión actual, el nuevo trabajador se activa y se vuelve responsable de las recuperaciones.
Eliminar cachés antiguos
También obtienes un eventoactivate. Esto generalmente se usa para hacer cosas que habrían roto la versión anterior mientras aún se estaba ejecutando, por ejemplo, deshacerse de los cachés antiguos. Esto también es útil para eliminar datos que ya no se necesitan para evitar llenar demasiado espacio en disco: cada navegador tiene un límite estricto en la cantidad de almacenamiento en caché que puede usar un determinadoservice worker. El navegador hace todo lo posible para administrar el espacio en disco, pero puede eliminar el almacenamiento en caché de un origen. El navegador, generalmente, eliminará todos los datos de un origen o ninguno de los datos de un origen.
Las promesas pasadas awaitUntil() bloquearán otros eventos hasta que se completen, por lo que puedes estar seguro de que tu operación de limpieza se habrá completado cuando obtengas tu primer eventofetch en el nuevoservice worker.
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
Chrome tienechrome://inspect/#service-workers, que muestra la actividad actual de losservice workers y el almacenamiento en un dispositivo, ychrome://serviceworker-internals, que muestra más detalles y te permite iniciar/detener/depurar el proceso del trabajador. En el futuro, tendrán modos de limitación/desconexión para simular conexiones defectuosas o inexistentes, lo que será algo realmente bueno.
Firefox también ha comenzado a implementar algunas herramientas útiles relacionadas con losservice workers:
- Puedes navegar a
about:debuggingpara ver quéSWs están registrados y actualizarlos/eliminarlos. - Al realizar pruebas, puedes sortear la restricción de HTTPS marcando la opción "Habilitarservice worker a través de HTTP (cuando la caja de herramientas está abierta)" en laConfiguración de herramientas de desarrollo de Firefox.
- El botón "Olvidar", disponible en las opciones de personalización de Firefox, se puede usar para borrar losservice workers y sus cachés (Error 1252998 en Firefox).
Nota:Puedes servir tu aplicación desdehttp://localhost (por ejemplo, usandome@localhost:/my/app$ python -m SimpleHTTPServer) para el desarrollo local. VeConsideraciones de seguridad