Web Worker
Getting your Trinity Audio player ready...

Estarás acostumbrado o acostumbrada a escuchar que JavaScript corre sobre un único hilo de ejecución. Esto puede en ocasiones acarrear problemas si una tarea es demasiada pesada, pues dejará bloqueado el hilo.

Para solventar ese problema, se dispone de los Web Workers, que permiten ejecutar código JavaScript en hilos que se lanzan en segundo plano. Cabe destacar que estos workers utilizan un contexto global distinto al de la ventana (o worker) que lo lanza. Como consejo puedes usar globalThis para reducir dependencias de usar window, self, etc.

El método de comunicación del worker con su creador es a través de mensajes gestionados como eventos. De forma muy parecida al sistema de comunicación entre iframes.

Pero como siempre, será mejor que veamos el funcionamiento de los workers con varios ejemplos.

Hola mundo de los workers

// Fichero index.js
// El constructor del worker recibe la ruta en la que se encuentra el fichero de JavaScript con el código
// del worker.
const worker = new Worker("worker.js");

// Definimos el callback para recibir los mensajes
worker.onmessage = function(event) {
  console.log("Message received from worker: " + event.data);
};

// Fichero worker.js
postMessage("Hello from the worker");

En el código superior se puede ver lo sencillo que es crear un Web Worker. La ejecución del código terminará mostrando en la consola: "Message received from worker: Hello from the worker".

A la hora de definir un worker, se especifica la ruta en la que se encuentra el script que se ejecutará en segundo plano.

Posteriormente, se pasa a escuchar los mensajes que envíe el worker asignando un callback al atributo onmessage. El callback asignado, recibirá como un objeto como parámetro que contendrá el atributo data con el contenido del mensaje enviado por el worker. Este contenido puede ser de cualquier tipo de dato.

Mensajería entre Worker y creador

En el caso de enviar un objeto en los mensajes, no hay problema de recursos compartidos, ya que se hace automáticamente un deep clone de dicho objeto. Este clonado se lleva a cabo gracias a la función structuredClone, que por si la desconoces, permite clonar objetos.

Todo esto lo veremos con un ejemplo que incluye algo de sobreingeniería, pero nos servirá para verificar que no hay paso de objetos por referencia.

// Fichero index.js
const worker = new Worker("worker.js");

const data = { value: 3 };

worker.onmessage = function(event) {
  console.log("Result", event.data);
  console.log("Old data", data);
};

worker.postMessage(data);

// Fichero worker.js
// Podemos omitir self o  globalThis para recibir mensajes en el worker.
onmessage = function(event) {
  const data = event.data;
  data.value = 123;
  postMessage(data);
};

Al ejecutar el código anterior, se podrá comprobar como en result se recibe un objeto con atributo value igual a 123; por otro lado el objeto data sigue teniendo el atributo value con valor 3. Esto es por lo que hemos comentado al principio de este apartado, los valores de los mensajes se pasan por valor y no por referencia.

Control de errores

Aparte de la mensajería estándar, es posible capturar aquellos errores que generen los Web Workers. Para ello asignaremos al atributo onerror del worker un callback que recibirá el evento con el error.

// Fichero index.js
const worker = new Worker("worker.js");

worker.onmessage = function(event) {
  console.log("Result", event.data);
};

worker.onerror = function(event) {
  console.error("Error on worker", event);
}

worker.postMessage(3);
worker.postMessage(-1);

// Fichero worker.js
onmessage = function(event) {
  const result = factorial(event.data);
  postMessage(result);
};

function factorial(value) {
  if (value < 0) {
    throw new Error("Factorial can't use negative values: " + value);
  }

  if (value === 0) {
    return 1;
  }

  let result = value;

  for (let index = value - 1; index > 0; --index) {
    result *= index;
  }

  return result;
}

La ejecución superior mostrará en consola:

Result: 6
Error on worker {
  colno: 15,
  composed: false,
  eventPhase: 0,
  filename: "http://127.0.0.1:8080/worker.js",
  lineno: 10,
  message: "Error: Factorial can't use negative values: -1",
  type: "error"
}

Como se puede apreciar, se recibe aparte del error generado, el fichero, así como la línea y columna en la que éste ha ocurrido.

Finalizar un worker

En el caso que queramos dar por finalizado un worker desde el creador, llamaremos al método terminate(), de forma que en este caso se termine la ejecución del worker, sin darle tiempo a realizar un "cierre ordenado".

Por otro lado, un worker puede indicar que ha terminado llamando a globalThis.close() (o self.close()).

En ambos casos, aunque se intenten enviar mensajes entre creado y worker, ya se habrá cerrado ese canal de comunicación.

Conclusión

Los Web Worker permiten llevar a cabo tareas pesadas fuera del hilo principal de JavaScript, y como habréis podido comprobar, es posible definirlos de forma muy sencilla. Tan sólo hay que especificar el fichero a utilizar como worker, y comunicar el creador y el worker con mensajes.

Finalmente, tan sólo remarcar, que no todos los objetos globales están disponibles en los Web Workers, puedes encontrar el listado completo en MDN.

Cualquier duda, como siempre, podéis dejarla en comentarios.

Comparte este artículo con quien quieras
Faker: usa datos realistas en tus pruebas y demos
4 servicios de redimensionado de imágenes online

Leave a Comment

Your email address will not be published. Required fields are marked *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.