HTML - API de Web Workers

-

Conceptos básicos de Web Worker

Los conceptos básicos de Web Worker abarcan la creación, comunicación y terminación de Web Workers. Para crear un Web Worker, se especifica el archivo de script que contiene el código del worker. La comunicación entre el script principal y el Web Worker se realiza mediante el paso de mensajes, utilizando el método postMessage() para enviar mensajes y el manejador de eventos onmessage para recibirlos. Cuando ya no se necesita un Web Worker, se puede terminar usando el método terminate().

Tipos de Web Workers

Tipo Descripción
Workers dedicados Los Workers dedicados son el tipo más común de Web Worker. Están asociados a un solo script y solo pueden comunicarse con ese script. Los Workers dedicados se crean usando el constructor Worker y son útiles para descargar tareas intensivas de CPU específicas de un script en particular.
Workers compartidos Los Workers compartidos permiten que múltiples scripts, incluso de diferentes ventanas o iframes, se comuniquen con un solo worker. Se crean usando el constructor SharedWorker. Los Workers compartidos son útiles para tareas que necesitan ser compartidas entre múltiples scripts o ventanas, como gestionar una caché compartida o proporcionar un canal de comunicación centralizado.
Service Workers Los Service Workers son un tipo especial de Web Worker que actúan como intermediarios entre las aplicaciones web, el navegador y la red. Están diseñados para interceptar solicitudes de red, almacenar recursos en caché y proporcionar funcionalidad sin conexión. Los Service Workers tienen un ciclo de vida diferente y se crean usando el método navigator.serviceWorker.register(). Se usan comúnmente para crear Aplicaciones Web Progresivas (PWA) y mejorar el rendimiento de las aplicaciones web.

Cada tipo de Web Worker tiene su propio caso de uso específico y modelo de comunicación. Los Workers dedicados son el tipo más simple, mientras que los Workers compartidos y los Service Workers ofrecen funcionalidades más avanzadas para escenarios específicos. Entender las diferencias entre estos tipos de Web Workers es importante para elegir el adecuado según tus necesidades e implementarlos correctamente en tu aplicación web.

Uso de Web Workers

Los Web Workers proporcionan un mecanismo para descargar tareas intensivas de CPU, realizar tareas en segundo plano y manejar scripts de larga duración sin bloquear el hilo principal de ejecución. Al usar Web Workers, puedes mejorar el rendimiento y la capacidad de respuesta de tus aplicaciones web.

Un caso de uso para Web Workers es descargar tareas intensivas de CPU. Cuando tienes operaciones computacionalmente pesadas, como algoritmos complejos, procesamiento de imágenes o análisis de datos, puedes mover esas tareas a un Web Worker. Esto permite que el hilo principal se mantenga receptivo, ya que los cálculos intensivos se realizan en un hilo separado. Los usuarios pueden continuar interactuando con la aplicación mientras el Web Worker maneja el trabajo intensivo de CPU en segundo plano.

Los Web Workers también son útiles para realizar tareas en segundo plano que no requieren interacción inmediata del usuario.

Consejo: Precarga de datos con Web Workers

// Hilo principal
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
    console.log('Datos cargados:', event.data);
};
worker.postMessage('start');
// worker.js
self.onmessage = function(event) {
    if (event.data === 'start') {
        // Precargar datos o realizar tarea en segundo plano
        self.postMessage('datos cargados');
    }
};

Al ejecutar estas tareas en un Web Worker, puedes asegurarte de que no interfieran con la capacidad de respuesta del hilo principal. El Web Worker puede trabajar en segundo plano, obteniendo datos o realizando actualizaciones, sin afectar la experiencia del usuario.

Otro escenario donde los Web Workers destacan es en el manejo de scripts de larga duración.

Consejo: Manejo de scripts de larga duración con Web Workers

// Hilo principal
const worker = new Worker('longTaskWorker.js');
worker.onmessage = function(event) {
    console.log('Tarea completada:', event.data);
};
worker.postMessage('start');
// longTaskWorker.js
self.onmessage = function(event) {
    if (event.data === 'start') {
        // Realizar tarea de larga duración
        let result = realizarCalculoComplejo();
        self.postMessage(result);
    }
};

function realizarCalculoComplejo() {
    // Lógica de cálculo complejo
    return 'resultado calculado';
}

Si tienes un script que tarda una cantidad considerable de tiempo en ejecutarse, como una tarea de procesamiento de datos compleja o una animación de larga duración, ejecutarlo en el hilo principal puede congelar la interfaz de usuario. Al mover el script de larga duración a un Web Worker, puedes mantener el hilo principal libre para responder a las interacciones del usuario. El Web Worker puede ejecutar el script de forma asíncrona, permitiendo que la aplicación siga siendo receptiva.

Limitaciones de los Web Workers

Aunque los Web Workers ofrecen beneficios, también tienen limitaciones. Es importante ser consciente de estas limitaciones al decidir si los Web Workers son adecuados para tu aplicación.

Limitación Descripción
Sin acceso al DOM Los Web Workers no tienen acceso al Modelo de Objetos del Documento (DOM). Un Web Worker no puede manipular ni actualizar directamente los elementos de la página web. Si un Web Worker necesita actualizar la IU, debe enviar un mensaje de vuelta al hilo principal, que puede entonces actualizar el DOM.
Sin acceso al objeto Window Los Web Workers no tienen acceso al objeto window. El objeto window proporciona acceso a características y APIs específicas del navegador, como alert(), prompt(), o location. Los Web Workers se ejecutan en un ámbito global separado y no pueden interactuar directamente con el objeto window.
Funciones JavaScript limitadas Los Web Workers tienen acceso limitado a ciertas funciones de JavaScript. Por ejemplo, los Web Workers no pueden acceder al objeto document, al objeto parent, o al objeto console. También tienen acceso restringido a algunas APIs del navegador, como el objeto XMLHttpRequest o el objeto localStorage.

A pesar de estas limitaciones, los Web Workers siguen siendo una herramienta para optimizar el rendimiento de las aplicaciones web. Al diseñar cuidadosamente la arquitectura de tu aplicación y el modelo de comunicación, puedes aprovechar los Web Workers para descargar tareas intensivas, realizar trabajo en segundo plano y manejar scripts de larga duración.

Comunicación del Web Worker

La comunicación del Web Worker implica enviar mensajes entre el script principal y el Web Worker, y recibir mensajes del Web Worker de vuelta al script principal. La comunicación se realiza a través del método postMessage() y el manejador de eventos onmessage.

Para enviar un mensaje a un Web Worker, se utiliza el método postMessage() desde el script principal. El método postMessage() toma el mensaje como argumento, que puede ser un valor simple o un objeto.

Consejo: Enviar un mensaje a un Web Worker

// Script principal
const worker = new Worker('worker.js');
worker.postMessage('Hola desde el script principal');

En el lado del Web Worker, puedes recibir el mensaje configurando un manejador de eventos onmessage. El objeto de evento pasado al manejador contiene los datos del mensaje en su propiedad data.

Consejo: Recibir un mensaje en el Web Worker

// worker.js
self.onmessage = function(event) {
    console.log('Mensaje recibido:', event.data);
};

De manera similar, el Web Worker puede enviar mensajes de vuelta al script principal usando el método postMessage(). El script principal puede recibir estos mensajes configurando su propio manejador de eventos onmessage.

Consejo: Enviar mensajes desde el Web Worker y recibirlos en el script principal

// worker.js
self.postMessage('Hola desde el Web Worker');

// Script principal
worker.onmessage = function(event) {
    console.log('Mensaje recibido del worker:', event.data);
};

Además de enviar valores simples u objetos, también puedes transferir datos usando objetos transferibles. Los objetos transferibles te permiten pasar datos grandes, como ArrayBuffer o MessagePort, entre el script principal y el Web Worker sin copiar los datos. Esto puede ayudar a mejorar el rendimiento cuando se trabaja con grandes conjuntos de datos. Para transferir datos, se pasa un array de objetos transferibles como segundo argumento al método postMessage().

Consejo: Transferir datos grandes con objetos transferibles

// Script principal
const largeData = new ArrayBuffer(1024 * 1024); // 1MB de datos
worker.postMessage({ data: largeData }, [largeData]);

Evento de mensaje

El evento message es el evento clave para manejar la comunicación entre el script principal y el Web Worker. Entender cómo funciona el evento message es importante para enviar y recibir mensajes correctamente.

Cuando se envía un mensaje usando el método postMessage(), se activa un evento message en el extremo receptor. El objeto de evento contiene las siguientes propiedades:

Propiedad Descripción
data Los datos del mensaje enviados por el remitente.
origin El origen de la ventana del remitente (script principal).
source Una referencia al objeto Worker del remitente (script principal) o MessagePort (Web Worker).

En el script principal, puedes manejar los mensajes del Web Worker configurando un manejador de eventos onmessage en el objeto Worker. El objeto de evento pasado al manejador contendrá los datos del mensaje en su propiedad data.

Consejo: Manejar mensajes del Web Worker en el script principal

// Script principal
worker.onmessage = function(event) {
    console.log('Mensaje recibido del worker:', event.data);
    // Manejar el mensaje y realizar acciones
};

De manera similar, en el script del Web Worker, puedes manejar los mensajes del script principal configurando un manejador de eventos onmessage en el objeto global self. El objeto de evento pasado al manejador contendrá los datos del mensaje en su propiedad data.

Consejo: Manejar mensajes del script principal en el Web Worker

// worker.js
self.onmessage = function(event) {
    console.log('Mensaje recibido:', event.data);
    // Manejar el mensaje y realizar acciones
    self.postMessage('Mensaje procesado');
};

Manejo de errores en Web Workers

El manejo de errores es una parte importante al trabajar con Web Workers. Implica manejar errores que pueden ocurrir en el script del Web Worker y enviar esos errores al script principal para tomar acciones.

Para manejar errores en Web Workers, necesitas escuchar eventos de error. El script del Web Worker puede activar un evento de error usando el manejador de eventos self.onerror. Este manejador de eventos se llama cuando ocurre un error en el script del Web Worker.

Consejo: Manejo de errores en Web Worker

// worker.js
self.onerror = function(event) {
    console.error('Error en Web Worker:', event.message);
    // Manejar el error o realizar tareas de limpieza
    self.postMessage({ error: event.message });
};

En el ejemplo anterior, el manejador de eventos self.onerror se usa para capturar cualquier error que ocurra en el script del Web Worker. Puedes registrar el mensaje de error, manejar el error o realizar cualquier tarea de limpieza necesaria.

Para enviar el error al script principal, puedes enviar un mensaje de error usando self.postMessage(). El script principal puede entonces escuchar este mensaje de error y tomar acciones.

Consejo: Manejo de errores en el script principal

// Script principal
worker.onmessage = function(event) {
    if (event.data.error) {
        console.error('Error recibido del Web Worker:', event.data.error);
        // Manejar el error en el script principal
    }
};

En el script principal, puedes verificar si el mensaje recibido tiene una propiedad de error. Si se recibe un error, puedes registrar el mensaje de error y manejarlo.

Errores comunes de Web Workers

Hay algunos errores comunes que puedes ver al trabajar con Web Workers:

Error Descripción
Script no encontrado Este error ocurre cuando no se puede encontrar o cargar el archivo de script del Web Worker. Asegúrate de que la ruta del archivo de script sea correcta y pueda ser accedida.
Script inválido Este error ocurre cuando el script del Web Worker tiene una sintaxis JavaScript inválida o no es un archivo de script válido. Revisa el script en busca de errores de sintaxis.
Límite de memoria excedido Los Web Workers tienen un límite de memoria establecido por el navegador. Si el script del Web Worker usa demasiada memoria, puede lanzar un error de "límite de memoria excedido". Haz que tu script maneje bien grandes conjuntos de datos y evita fugas de memoria.

Consejo: Error de script inexistente

// Script principal
const worker = new Worker('script-inexistente.js'); // Lanza un error

Consejo: Error de sintaxis común en Web Worker

// worker.js
console.log('Hola desde Web Worker') // Falta punto y coma

Consejo: Límite de memoria excedido en Web Worker

// worker.js
const datoGrande = new Array(1000000000); // Asignando un array grande

Al manejar errores en Web Workers y escuchar eventos de error, puedes manejar y recuperarte de errores, proporcionando una mejor experiencia de usuario y manteniendo tu aplicación web estable.

Recuerda probar bien tus scripts de Web Worker y manejar errores tanto en el script del Web Worker como en el script principal. Un buen manejo de errores asegura que tu aplicación pueda manejar situaciones inesperadas y proporcionar retroalimentación útil al usuario cuando sea necesario.

Técnicas Avanzadas de Web Worker

Los Web Workers ofrecen técnicas avanzadas que permiten ampliar su funcionalidad y usarlos con otras características y APIs de JavaScript. Veamos algunas de estas técnicas avanzadas.

Importar scripts en Web Workers es una forma de modularizar y reutilizar código dentro de un Web Worker. Al usar la función importScripts(), puedes cargar archivos JavaScript externos en el ámbito del Web Worker. Esto te permite dividir tu código de worker en archivos separados para una mejor organización y mantenimiento.

Consejo: Importando Scripts en Web Workers

// worker.js
importScripts('utility.js', 'math.js');

self.onmessage = function(event) {
    // Usa funciones o variables de los scripts importados
    const result = performCalculation(event.data);
    self.postMessage(result);
};

Usar Web Workers con promesas es otra técnica poderosa. Las promesas proporcionan una forma de manejar operaciones asíncronas y mejorar la legibilidad del código. Al envolver la comunicación entre el script principal y el Web Worker dentro de promesas, puedes crear un flujo de datos más estructurado e intuitivo.

Consejo: Usando Web Workers con Promesas

// Script principal
function performTask(data) {
    return new Promise((resolve, reject) => {
        const worker = new Worker('worker.js');
        worker.onmessage = function(event) {
            resolve(event.data);
        };
        worker.onerror = function(error) {
            reject(error);
        };
        worker.postMessage(data);
    });
}

// Uso
performTask('Datos de la tarea')
    .then(result => {
        console.log('Resultado de la tarea:', result);
    })
    .catch(error => {
        console.error('Error en la tarea:', error);
    });

Combinar Web Workers con otras APIs abre nuevas posibilidades. Por ejemplo, puedes usar Web Workers con la API de Archivo para realizar tareas de procesamiento de archivos en segundo plano. Esto te permite leer y modificar archivos sin bloquear el hilo principal.

Consejo: Combinando Web Workers con la API de Archivo

// Script principal
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', function(event) {
    const file = event.target.files[0];
    const worker = new Worker('fileWorker.js');
    worker.onmessage = function(event) {
        console.log('Archivo procesado:', event.data);
    };
    worker.postMessage(file);
});

// fileWorker.js
self.onmessage = function(event) {
    const file = event.data;
    // Procesa el archivo en el Web Worker
    // ...
    self.postMessage('Procesamiento de archivo completado');
};

Mejores Prácticas para Web Workers

Al usar Web Workers, es importante seguir las mejores prácticas para aprovecharlos al máximo y evitar problemas comunes. Aquí hay algunos puntos clave a considerar:

Consideración Descripción
Cuándo usar Web Workers - Usa Web Workers para tareas intensivas en CPU que pueden ejecutarse en segundo plano.
- Evita usar Web Workers para tareas que requieren comunicación frecuente con el hilo principal.
- Considera el costo de crear y gestionar Web Workers para tareas pequeñas o de corta duración.
Rendimiento - Ten en cuenta la cantidad de datos que se transfieren entre el hilo principal y los Web Workers.
- Usa objetos transferibles (ArrayBuffer, MessagePort) para evitar copiar grandes cantidades de datos entre hilos.
- Minimiza la frecuencia del intercambio de mensajes para reducir la sobrecarga de comunicación.
Seguridad - Asegúrate de que los scripts de Web Worker se sirvan desde el mismo origen que el script principal para evitar problemas de seguridad de origen cruzado.
- Ten cuidado al usar importScripts() para cargar scripts externos, ya que pueden introducir vulnerabilidades de seguridad.
- Valida y sanitiza cualquier dato recibido de los Web Workers para prevenir ataques de inyección.

Ejemplos de Web Workers

Los Web Workers proporcionan una forma de mover tareas del hilo principal y mejorar el rendimiento de las aplicaciones web. Veamos algunos ejemplos que muestran el uso de Web Workers en diferentes situaciones.

Consejo: Ejemplo Simple de Web Worker

// Script principal (main.js)
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
  console.log('Resultado recibido del worker:', event.data);
};
worker.postMessage(5);

// Script del worker (worker.js)
self.onmessage = function(event) {
  const number = event.data;
  const result = fibonacci(number);
  self.postMessage(result);
};

function fibonacci(n) {
  if (n <= 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

En este ejemplo simple, el script principal crea un nuevo Web Worker y le envía un número usando postMessage(). El script del worker recibe el mensaje, calcula el número de Fibonacci usando una función recursiva y envía el resultado de vuelta al script principal usando postMessage(). El script principal registra el resultado recibido en la consola.

Consejo: Ejemplo Complejo de Web Worker

// Script principal (main.js)
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
  const { type, data } = event.data;
  if (type === 'progress') {
    updateProgressBar(data.progress);
  } else if (type === 'result') {
    displayResult(data.result);
  }
};
worker.postMessage({ type: 'start', data: { /* datos complejos */ } });

// Script del worker (worker.js)
self.onmessage = function(event) {
  const { type, data } = event.data;
  if (type === 'start') {
    processData(data);
  }
};

function processData(data) {
  // Realizar procesamiento de datos complejo
  for (let i = 0; i < data.length; i++) {
    // Procesar elemento de datos
    const progress = ((i + 1) / data.length) * 100;
    self.postMessage({ type: 'progress', data: { progress } });
  }
  const result = { /* datos procesados */ };
  self.postMessage({ type: 'result', data: { result } });
}

En este ejemplo más complejo, el script principal envía un mensaje al worker con un tipo y datos específicos. El script del worker recibe el mensaje, verifica el tipo de mensaje y realiza la acción apropiada. En este caso, cuando el tipo de mensaje es 'start', el worker procesa los datos recibidos. Durante el procesamiento, el worker envía actualizaciones de progreso de vuelta al script principal usando postMessage(). Una vez que el procesamiento está completo, el worker envía el resultado final de vuelta al script principal. El script principal maneja los mensajes recibidos según su tipo, actualizando la barra de progreso o mostrando el resultado.

Casos de uso reales para Web Workers

Caso de uso Descripción
Procesamiento de datos en segundo plano Los Web Workers pueden usarse para procesar grandes conjuntos de datos o realizar cálculos complejos en segundo plano sin afectar la capacidad de respuesta del hilo principal. Esto es útil para aplicaciones que manejan análisis de datos, procesamiento de imágenes o simulaciones científicas.
Solicitudes API asíncronas Los Web Workers pueden usarse para hacer solicitudes API asíncronas y manejar las respuestas sin bloquear el hilo principal. Esto mejora el rendimiento percibido de las aplicaciones web al permitir que la interfaz de usuario permanezca receptiva mientras espera las respuestas de la API.
Actualizaciones en tiempo real Los Web Workers pueden usarse para implementar actualizaciones en tiempo real en aplicaciones web. Por ejemplo, un Web Worker puede ser responsable de recibir actualizaciones de un servidor a través de WebSocket o long-polling y notificar al hilo principal para actualizar la interfaz de usuario. Esto mantiene el hilo principal libre para manejar las interacciones del usuario.
Preprocesamiento de datos para visualización Los Web Workers pueden usarse para preprocesar grandes conjuntos de datos antes de visualizarlos en el hilo principal. Esto puede implicar filtrar, agregar o transformar los datos para reducir la carga de trabajo en el hilo principal y mejorar el rendimiento de renderizado de gráficos o diagramas.

Estos son solo algunos ejemplos de cómo los Web Workers pueden usarse en escenarios del mundo real. La flexibilidad y potencia de los Web Workers los hacen valiosos para mover tareas, realizar procesamiento en segundo plano y mejorar el rendimiento general de las aplicaciones web.