Introducción
Recuerdo que utilicé los Web Workers en JavaScript una vez que tenía que generar un PDF muy grande. Así, aunque fuera un proceso tardado, la interfaz de usuario no se quedaba congelada.
Veamos entonces un demo o ejemplo y la explicación de los web workers o webworkers en el lenguaje de programación JavaScript
Probar proyecto terminado
Si quieres ver la diferencia entre usar o no usar los Web Workers aquí dejo estos enlaces para calcular números primos, cosa que es muy tardada.
Sigue leyendo si quieres saber más.
¿Qué es un Web Worker en JavaScript?
Es una nueva (no tan nueva pero su uso no está tan extendido) API que nos permite ejecutar procesos en segundo plano. Recordemos por ejemplo las llamadas asíncronas, se hacen para no congelar la interfaz del usuario.
Por ejemplo, si no existiera el código asíncrono e hiciéramos una petición GET cuya duración fuera de 10 segundos, el usuario no podría hacer nada (no hacer click en botones, no ver animaciones) por 10 segundos.
Entonces cuando hacemos un proceso pesado o largo que no puede ser asíncrono, usamos los Web Workers en Javascript.
¿Cómo funcionan los web workers en JavaScript?
Se comunican a través de mensajes, así como cuando llamamos a funciones. Para pasarles datos llamamos a la función postMessage
.
Y cuando el worker quiere hablarnos, dispara el evento onmessage
.
Ah, antes de que lo olvide. Los Workers no pueden manipular directamente al DOM, sólo pasan o reciben mensajes y procesan datos.
Ejemplo de uso de Web WorkerS en JavaScript
Vamos a calcular números primos. Mejor dicho, buscaremos cuál es el número primo más grande menor al número 150, 000.
Números primos
Los números primos son aquellos que sólo son divisibles entre 1 y entre ellos mismos.
Por ejemplo, el 11 es primo porque no puede ser dividido entre 2, ni entre 3, 4, 5, 6, 7 8, 9 o 10. Se dice que “puede ser dividido” cuando su residuo es cero.
Pero no vamos a ver el algoritmo, vamos a calcular números primos “a lo tonto” para que sea un proceso tardado. Me he robado de por ahí un algoritmo para determinar si un número es primo:
function primo(numero) {
for (var i = 2; i < numero; i++) {
if (numero % i === 0) {
return false;
}
}
return numero !== 1;
}
Ahora en el Web Worker vamos a comprobar cuál es el mayor número primo menor a 150,000
Programación del worker para el cálculo
El código completo del worker queda así:
/**
* Ejemplo de Web Worker calculando primos
* @author parzibyte.me/blog
*/
/*
Esto será ejecutado únicamente cuando le pasamos datos al worker
Si no queremos llamarlo y queremos que se ejecute tan pronto lo instanciamos,
simplemente quitamos la función y dejamos todo el código afuera.
La ventaja de esperar a onmessage es que podemos recibir parámetros
*/
onmessage = function(evento){
var limite = evento.data; // Lo que recibimos de la llamada
var mayorEncontradoHastaElMomento = 0; // Aquí guardaremos el mayor
function esPrimo(numero) {
for (var i = 2; i < numero; i++) {
if (numero % i === 0) {
return false;
}
}
return numero !== 1;
}
for(var x = 0; x < limite; x++){
if(esPrimo(x)){
mayorEncontradoHastaElMomento = x;
}
}
//Cuando termine el for, llamamos al método
// postMessage para informar a nuestro invocador
// que hemos terminado nuestra larga e importante tarea
postMessage("Hola. El número primo más grande menor que " + limite.toString() + " es " + mayorEncontradoHastaElMomento.toString());
}
Como vemos es un ciclo for
, vamos comprobando por cada número si éste es primo y en caso de que sí, lo tomamos como el mayor encontrado.
Cuando salimos del ciclo for
llamamos a la función postMessage
. Esta función es la más importante, pues sirve para enviar mensajes ya sea del hilo principal al worker, o del worker al hilo principal.
También es importante la función onmessage
, pues es llamada cuando recibimos un mensaje.
Llamada e inclusión del web worker en una página web
Ahora en nuestro hilo principal (esto es en nuestra página HTML) tenemos este código:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Calcular número primo con Web Workers</title>
</head>
<body>
<h1>Vamos a esperar la respuesta del Web Worker</h1>
<p>El botón de abajo es para demostrar que podemos hacer click porque no es código bloqueante</p>
<button>Soy un botón y puedes hacerme click cuando quieras</button>
<p id="resultados">Aquí aparecerá el mensaje</p>
</body>
<script>
//Primero vemos si el navegador lo soporta
if("undefined" !== typeof Worker){
var miWorker = new Worker("worker.js"); // Como argumento le pasamos la ruta del script
miWorker.postMessage(150000); // Le decimos que calcule el mayor primo menor que 150000
miWorker.onmessage = function(evento){
var elemento = document.querySelector("#resultados"); // Obtener el párrafo
//Por cierto, el mensaje que pasamos está en data
elemento.innerHTML = evento.data;
}
}
</script>
</html>
Por favor lee el código, tiene comentarios que lo explican.
Es importante mencionar que debemos servir la página en HTML en un servidor, puede ser Apache sobre Windows o cualquiera que prefieras.
El punto es que no debemos abrirlo con file:///, sino con un servidor HTTP.
Probando web worker
Ahora cuando navegamos a la página en un inicio se ve esto (al menos en mi lenta computadora):
Pero después de unos segundos (si tu computadora es muy rápida puedes hacer más grande el número, y si es más lenta, lo contrario) aparece el mensaje:
No sé si es cierto que el número es 149, 993. Lo que sí sé es que el worker se ejecutó con éxito en segundo plano sin bloquear mi interfaz.
Ahora se me ocurre algo muy interesante, ¿qué pasaría si no usamos Web Workers en JavaScript? veamos cómo no podremos hacer click en el botón.
Si los workers no existieran…
Veamos entonces cómo hacer lo mismo pero sin web worker. Todo quedaría en el mismo archivo:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Calcular número primo sin Web Workers</title>
</head>
<body>
<h1>Vamos a esperar a que el cálculo termine</h1>
<p>El botón de abajo es para demostrar que NO podemos hacer click porque SÍ es código bloqueante</p>
<button>Soy un botón y NO puedes hacerme click mientras se trabaja</button>
<p id="resultados">Aquí aparecerá el mensaje</p>
</body>
<script>
//Este setTimeout es para esperar 2 segundos antes de ejecutar todo el código de abajo
// De esta forma demostramos que primero todo va bien pero luego la interfaz "se congela"
window.setTimeout(function(){
var limite = 150000; // Lo definimos desde aquí
var mayorEncontradoHastaElMomento = 0; // Aquí guardaremos el mayor
function esPrimo(numero) {
for (var i = 2; i < numero; i++) {
if (numero % i === 0) {
return false;
}
}
return numero !== 1;
}
for(var x = 0; x < limite; x++){
if(esPrimo(x)){
mayorEncontradoHastaElMomento = x;
}
}
var elemento = document.querySelector("#resultados"); // Obtener el párrafo
//Escribir el resultado
elemento.innerHTML = "Hola. El número primo más grande menor que " + limite.toString() + " es " + mayorEncontradoHastaElMomento.toString();
}, 2000);
</script>
</html>
Usamos un setTimeout para esperar antes de ejecutar el proceso pesado. Así, al inicio sí podemos clickear al botón pero luego de 2 segundos no podemos hacer nada hasta que el proceso termina.
Esta es la gran ventaja de los web workers en JavaScript: que no son bloqueantes y procesan tareas difíciles en un hilo diferente al principal.
Repaso y conclusión
- Sirven para ejecutar tareas costosas en segundo plano
- Reciben mensajes a través del evento onmessage. Y envían mensajes con postMessage. Igualmente podemos llamar al método postMessage para llamar al worker, y escuchar su evento onmessage para cuando él nos hable
- No están disponibles en todos los navegadores aunque sí en la gran mayoría
Recuerda que puedes probar los ejemplos directamente en tu navegador