Webworkers

Es una especificación que permite procesar código en paralelo a la página principal.

En el navegador, el código de JavaScript realiza las tareas de manera secuencial, es decir espera a que se termine una actividad antes de continuar con la(s) siguiente(s) actividad(es).

Al utilizar los webworkers los procesos se realizan en un canal de proceso independiente al canal principal (hilo) dejando al navegador libre para realizar otras tareas.

Esta funcionalidad está disponible para IE10 y superiores – lista de compatibilidad más detallada está en Can I Use.

Algunas desventajas de los webworkers son:
– No se puede interactuar con el DOM – el código se ejecuta dentro del objeto Dedicated Worker Global Scope en lugar del objeto window – por eso no es posible modificar el HTML.
– No se pueden utilizar variables globales ni para obtener valores o establecer valores.

Para comunicarse entre la página principal y un webworker se puede enviar un mensaje.
Este mensaje únicamente acepta un solo valor.
Si se necesita enviar más valores el mensaje puede ser un objeto o un arreglo.

Documento principal

En el documento principal la referencia hacia el webworker se realiza utilizando el nombre del objeto del trabajador.

1. Crear un webworker asociado a un script.
2. Utilizar la función postMessage en el webworker.
Esto va a enviar un mensaje al script que está asociado el trabajador.
3. Tener una función onMessage para recibir el mensaje del webworker cuando termine de realizar la función.
4. Se puede mandar llamar la función terminate en caso que se quiera finalizar la tarea del webworker.

WebWorker

Dentro del webworker la referencia hacia el webworker se realiza utilizando self.

1. Definir una función onmessage.
Esta función se va a ejecutar cuando en la página principal se utilice postMessage.
2. Si existe un mensaje (parámetros) va como atributo data del evento – event.data.
3. Cuando termina de procesar envía un mensaje al script que lo mandó llamar utilizando postMessage.
4. Si se quiere deshabilitar el trabajador se utiliza la función close.

Referencia

Documento con la funcionalidad dentro

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
  body { width: 700px; margin: 0 auto; background: #f0f0f0;}
  #contenido {padding: 10px; background: #fff; color: #151515;}
  </style>
</head>
<body>

<div class="contenido">
  Webworkers
  <div id='inicio'></div>
  <div id='termino'></div>
  <div id='mensaje'></div>
  <input id='comenzar' type='button' value='Comenzar'>
</div>

<script>

var inicio = document.getElementById('inicio');
var termino = document.getElementById('termino');
var mensaje = document.getElementById('mensaje');
var comenzar = document.getElementById('comenzar');
var color = 0;
var colores = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'lightgray'];
var fondo; 

comenzar.addEventListener('click', function () {
    iniciar();    
    finalizar();    
});

function procesar() {
  var i = 0;
  var max = 2000000000;    
  while (i < max) {
    i++;
  }
}

function cambiar_color_fondo() {
  document.getElementsByTagName('body')[0].style.background = colores[color];
  color == colores.length ? color = 0 : color++;
}

function iniciar() {
  inicio.innerHTML = 'Iniciar: ' + new Date();    
  termino.innerHTML = 'Terminar: ';
  fondo = setInterval(cambiar_color_fondo, 500);
  procesar();
}

function finalizar() {
  clearInterval(fondo);
  termino.innerHTML = 'Terminar: ' + new Date();
}


</script>
</body>
</html>

Documento utilizando un webworker – proceso.js – para realizar la tarea que toma mucho tiempo

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
  body { width: 700px; margin: 0 auto; background: #f0f0f0;}
  #contenido {padding: 10px; background: #fff; color: #151515;}
  </style>
</head>
<body>

<div class="contenido">
  Webworkers
  <div id='inicio'></div>
  <div id='termino'></div>
  <div id='mensaje'></div>
  <input id='comenzar' type='button' value='Comenzar'>
</div>


<script>
var inicio = document.getElementById('inicio');
var termino = document.getElementById('termino');
var mensaje = document.getElementById('mensaje');
var comenzar = document.getElementById('comenzar');
var color = 0;
var colores = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'lightgray'];
var fondo; 
var worker = new Worker("proceso.js");

comenzar.addEventListener('click', function () {
  iniciar();
  
  
});

worker.onmessage = function(e) {
  finalizar(e.data);
}



function cambiar_color_fondo() {
  document.getElementsByTagName('body')[0].style.background = colores[color];
  color == colores.length ? color = 0 : color++;
}

function iniciar() {
  inicio.innerHTML = 'Iniciar: ' + new Date();    
  termino.innerHTML = 'Terminar: ';
  fondo = setInterval(cambiar_color_fondo, 500);
  worker.postMessage();
}

worker.onerror = function (e) {
//  alert(event.type)
console.log('ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message);
}

function finalizar(mensaje) {
  clearInterval(fondo);
  termino.innerHTML = mensaje.mensaje;
}

</script>
</body>
</html>

WebWorker – proceso.js –

self.onmessage = function(e) {
  var i = 0;
  var max = 2000000000;  

  while (i < max) {
    i++;
  }
  

  var mensaje = {
    mensaje: "Terminar: " + new Date(),
    worker: true    
  }

  self.postMessage(mensaje);  

  self.close();
  

};

Documento similar al que utiliza el webworker. En este caso el script principal es externo – main.js

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
  body { width: 700px; margin: 0 auto; background: #f0f0f0;}
  #contenido {padding: 10px; background: #fff; color: #151515;}
  </style>
</head>
<body>

<div class="contenido">
  Webworkers
  <div id='inicio'></div>
  <div id='termino'></div>
  <div id='mensaje'></div>
  <input id='comenzar' type='button' value='Comenzar'>
</div>


<script src='main.js'></script>
</body>
</html>

Script externo – main.js –

var inicio = document.getElementById('inicio');
var termino = document.getElementById('termino');
var mensaje = document.getElementById('mensaje');
var comenzar = document.getElementById('comenzar');
var color = 0;
var colores = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'lightgray'];
var fondo; 
var worker = new Worker("proceso.js");

comenzar.addEventListener('click', function () {
  iniciar();
  
  
});

worker.onmessage = function(e) {
  finalizar(e.data);
}



function cambiar_color_fondo() {
  document.getElementsByTagName('body')[0].style.background = colores[color];
  color == colores.length ? color = 0 : color++;
}

function iniciar() {
  inicio.innerHTML = 'Iniciar: ' + new Date();    
  termino.innerHTML = 'Terminar: ';
  fondo = setInterval(cambiar_color_fondo, 500);
  worker.postMessage();
}

worker.onerror = function (e) {
//  alert(event.type)
console.log('ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message);
}

function finalizar(mensaje) {
  clearInterval(fondo);
  termino.innerHTML = mensaje.mensaje;
}

Documento con la funcionalidad dentro. A diferencia de la primer versión que utiliza un worker, en esta versión se verifica si el navegador soporta la funcionalidad. Si no la soporta no se llama la función para cambiar los colores de fondo.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
  body { width: 700px; margin: 0 auto; background: #f0f0f0;}
  #contenido {padding: 10px; background: #fff; color: #151515;}
  </style>
</head>
<body>

<div class="contenido">
  Webworkers
  <div id='inicio'></div>
  <div id='termino'></div>
  <div id='mensaje'></div>
  <input id='comenzar' type='button' value='Comenzar'>
</div>


<script>
var inicio = document.getElementById('inicio');
var termino = document.getElementById('termino');
var mensaje = document.getElementById('mensaje');
var comenzar = document.getElementById('comenzar');
var color = 0;
var colores = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'lightgray'];
var fondo; 
var worker;



function procesar() {
  var i = 0;
  var max = 2000000000;    
  while (i < max) {
    i++;
  }

  var mensaje = {
    mensaje: "Terminó: " + new Date(),
    worker: false    
  }

  finalizar(mensaje);

}



comenzar.addEventListener('click', function () {
  iniciar();
});


function cambiar_color_fondo() {
  document.getElementsByTagName('body')[0].style.background = colores[color];
  color == colores.length ? color = 0 : color++;
}

function iniciar() {
  inicio.innerHTML = 'Iniciar: ' + new Date();    
  termino.innerHTML = 'Terminar: ';
  
  if(!window.Worker){
    procesar();
  } else {
    worker = new Worker("proceso.js");
    worker.postMessage();
    fondo = setInterval(cambiar_color_fondo, 500);    
    worker.onmessage = function(e) {
      finalizar(e.data);
    }
  }
  
}

function finalizar(mensaje) {
  if (mensaje.worker) {
    clearInterval(fondo);      
  }
  
  termino.innerHTML = mensaje.mensaje;
}

</script>
</body>
</html>

Tip:
En el webworker se utilizar librerías externas utilizando importScripts.
importScripts(‘lib1.js’, ‘lib2.js’);

Como es una especificación reciente es conveniente preguntar si el navegador la puede realizar. En caso de que el navegador no sea compatible podemos incorporar la funcionalidad o evitar mostrar el control en la interfaz para que no se desencadene.

Nota:
En Chrome para poder utilizar los webworkers con el protocolo file (sin necesidad te utilizar un servidor web) hay que habilitar la opción –allow-file-access-from-files al iniciar la aplicación.

Windows – desde CMD.
C:\Ruta_a_carpeta_chrome\Chrome.exe –allow-file-access-from-files

OS X – desde la terminal.
open /Applications/Google\ Chrome.app –args –allow-file-access-from-files