Multithreading con Javascript: Web Workers.

Sebastian Vega

Sebastian Vega

Compartir

Multithreading con Javascript: Web Workers.

Ya les he contado algo sobre los Service Workers y lo importante que son para las PWAs. Esta vez, vamos a ver de donde vienen, la especificación de los Web Workers y otros tipos de workers.

Qué es esto de Web Workers? Seré yo uno si trabajo con la web?… Nada de eso, los Web Workers son una de las API que están revolucionando la forma en que trabajamos en el front y utilizamos los navegadores.

¿Qué son?

Javascript, es un lenguaje mono-hilo, esto quiere decir que la interpretación de nuestro código se atiende por un único hilo de ejecución. Por lo mismo se programa de manera no bloqueante y frecuentemente utilizamos llamadas asíncronas.

Por otro lado, existe la especificación de los Web Workers, que básicamente nos permiten ejecutar código en otros hilos de ejecución, esto no quiere decir que Javascript sea multi-hilo, sino que es una característica del browser, a la que se puede acceder desde Javascript y permite la ejecución en paralelo de tareas. Los web workers son ideales mantener la UI libre de bloqueos cuando hay que lidiar con un montón de cálculos o procesamientos costosos.

Según la especificación, existen los siguientes tipos de Web Workers:

  • Dedicated Workers: Son instanciados por el proceso principal y solamente pueden comunicarse con el. Veamos un poco de código, basado en un ejemplo que podemos encontrar en github, el que aquí se puede ver funcionando.
//index.html
<html>
 <head><title>Web Workers basic example</title></head> 
 <body> 
  <h1>Web<br>Workers<br>basic<br>example</h1> 
  <div class=”controls” tabindex=”0"> 
   <form> 
    <div> 
     <label for=”number1">Multiply number 1: </label> 
     <input type=”text” id=”number1" value=”0"> 
    </div> 
    <div> 
     <label for=”number2">Multiply number 2: </label> 
     <input type=”text” id=”number2" value=”0"> 
    </div> 
   </form> 
   <p class=”result”>Result: 0</p> 
  </div> 
 </body> 
 <script src=”main.js”></script>
</html>
                                              
//main.js
var first = document.querySelector('#number1');
var second = document.querySelector('#number2'); 
var result = document.querySelector('.result'); 
if (window.Worker) { //Chequea si el navegador soporta el api Worker
 // Creamos el worker con la ruta del script.
 var myWorker = new Worker("worker.js"); 
 //Eventos para enviar el mensaje al worker.
 first.onchange = function() {    
  myWorker.postMessage([first.value,second.value]);  
  console.log('Message posted to worker'); 
 };  
 second.onchange = function() {       
  myWorker.postMessage([first.value,second.value]);    
  console.log('Message posted to worker'); 
 };  
 //Función onmessage para recibir los mensajes del worker
 myWorker.onmessage = function(e) {  
  result.textContent = e.data;  
  console.log('Message received from worker'); 
 };
}
//worker.js. Se implementa función onmessage en el worker para recibir los mensajes del hilo principal.
onmessage = function(e) { 
 console.log(‘Message received from main script’); 
 var workerResult = ‘Result: ‘ + (e.data[0] * e.data[1]);       
 console.log(‘Posting message back to main script’);       
 postMessage(workerResult);
}

Shared Workers: Es accesible por múltiples scripts que se originen del proceso principal, por ejemplo otros tabs del navegador, iframes, incluso otros shared workers. Veamos un poco de código, basado en un ejemplo que podemos encontrar en github, el que aquí se puede ver funcionando.

//index.html
<html>
 <head><title>Shared Workers basic example</title></head>
 <body>
  <div class="controls" tabindex="0">
    <form>
      <div>
        <label for="number1">Multiply number 1: </label>
        <input type="text" id="number1" value="0">
      </div>
      <div>
        <label for="number2">Multiply number 2: </label>
        <input type="text" id="number2" value="0">
      </div>
    </form>
    <p class="result1">Result: 0</p>
    <p><a href="index2.html" target="_blank">Go to second worker page</a></p>
  </div>
  <script src="multiply.js"></script>
  <script src="nosubmit.js"></script>
 </body>
</html>
//index2.html
<html>
  <head><title>Shared Workers basic example</title></head>
  <body>
   <div class="controls" tabindex="0">
    <form>
      <div>
        <label for="number3">Square number: </label>
        <input type="text" id="number3" value="0">
      </div>
    </form>
    <p class="result2">Result: 0</p>
   </div>
   <script src="square.js"></script>
   <script src="nosubmit.js"></script>
  </body>
</html>
//multiply.js
var first = document.querySelector('#number1');
var second = document.querySelector('#number2');
var result1 = document.querySelector('.result1');
if (!!window.SharedWorker) {
  var myWorker = new SharedWorker("worker.js");
first.onchange = function() {
    myWorker.port.postMessage([first.value, second.value]);
    console.log('Message posted to worker');
  }
second.onchange = function() {
    myWorker.port.postMessage([first.value, second.value]);
    console.log('Message posted to worker');
  }
myWorker.port.onmessage = function(e) {
    result1.textContent = e.data;
    console.log('Message received from worker');
    console.log(e.lastEventId);
  }
}
//square.js
var squareNumber = document.querySelector('#number3');
var result2 = document.querySelector('.result2');
if (!!window.SharedWorker) {
  var myWorker = new SharedWorker("worker.js");
squareNumber.onchange = function() {
    myWorker.port.postMessage([squareNumber.value, squareNumber.value]);
    console.log('Message posted to worker');
  }
myWorker.port.onmessage = function(e) {
    result2.textContent = e.data;
    console.log('Message received from worker');
  }
}
//worker.js
onconnect = function(e) {
  var port = e.ports[0];
port.onmessage = function(e) {
    var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
    port.postMessage(workerResult);
  }
}

  • Service Workers: Están diseñados para manejar las peticiones de red y actuar como proxy entre las aplicaciones web, el navegador y la conexión de red. Dada su capacidad para interceptar y manejar solicitudes de red es un api muy útil a la hora de soportar experiencias offline, cachear información, manejar notificaciones push y sincronizar datos en segundo plano. Cabe destacar que, al ejecutarse en otro hilo, desde los service workers no tenemos acceso al DOM directamente, es decir podríamos comunicarnos con el hilo principal y realizar acciones programáticamente para ello. El caso de service workers lo abordaremos en una siguiente historia en mayor profundidad, veremos su ciclo de vida y un ejemplo práctico de uso.

Referencias:

https://html.spec.whatwg.org/multipage/workers.html#the-worker's-lifetime