jueves, 7 de febrero de 2013

HTML5: Introducción a los Web Workers 1


Uno de los mayores inconvenientes que tiene javascript como lenguaje de programación es que es un entorno de subproceso único. Esto implica que en procesos largos y costosos podemos dejar "congelado" el navegador del cliente.
Supongamos la siguiente página
<input id="txtValue" type="text" /><input id="btnProcess" onclick="_process()" type="button" value="Calculate primes" /><div id="output">
Y el siguiente código en javascript
function _process() {
    var value = document.querySelector('#txtValue').value;
    var primes = getPrimes(value);

    var output = document.querySelector('#output');
    output.innerHTML = primes.toString();
}

// Devuelve un array con los números primos comprendidos entre 1 y value.
function getPrimes(value) {
    var primes = [];
            
    for (var i = 1; i <= value; i++) {
        if (_isPrime(i))
            primes.push(i);                
    }

    return primes;
}

// Devuelve true si un número es primo, false en otro caso.
function isPrime(n) {
    if (n < 2) return false;
    var m = Math.sqrt(n);
    for (var i = 2; i <= m; i++)
        if (n % i == 0) return false;
    return true;
}
Si ponemos un valor elevado, por ejemplo un millón, vemos que durante un par de segundos nuestra página deja de responder.

Los Web Workers aparecen con la especificación de HTML5 y se definen como un script que se ejecuta en background independientemente de otros scripts y sin que afecte al rendimiento de la página.

Modificar el comportamiento del ejemplo anterior para que se ejecute en un Web Worker es bastante sencillo. La forma más fácil de hacer es aislar el código de nuestro Web Worker en un fichero y crear a partir de él nuestro objeto Worker. Para hacer esto haremos lo siguiente en nuestra página web
var worker = new Worker('webworker.js');
worker.addEventListener('message', _message, false);

function _message(e) {
    var output = document.querySelector('#output');
    output.innerHTML = e.data.toString();
}
En la primera linea creamos nuestro Worker a partir del código que tengamos en el fichero webworker.js, y en la segunda linea nos "enganchamos" al evento message del Worker, ya que a través de este evento es como se comunicará nuestro Worker con nosotros (la página web). Sólo nos falta saber como nos comunicaremos con nuestro Worker. Esto también es bastante sencillo y lo haremos de la siguiente manera
function _process() {           
    var value = document.querySelector('#txtValue').value;
    worker.postMessage(value);
}
Este método será llamado cada vez que pulsemos el botón "Calculate primes". En el extraemos el valor que queremos procesar y lo pasamos como parámetro en el método postMessage. Este método es la forma que tenemos de comunicarnos con el Worker.

Construir el Worker también es bastante fácil y en él se aplican los mismo métodos y eventos que hemos comentado anteriormente. El Worker recibe la información de la página a través del evento message y se comunicará con la página a través del método postMessage. Sabiendo esto el código del Worker quedaría de la siguiente forma
addEventListener('message', _message, false);

// Procesamos el mensaje recibido
function _message(e) {
    var primes = _getPrimes(e.data);
    self.postMessage(primes);
}

// Devuelve un array con los números primos comprendidos entre 1 y value.
function _getPrimes(value) {
    var primes = [];

    for (var i = 1; i <= value; i++) {
        if (_isPrime(i))
            primes.push(i);
    }

    return primes;
}

// Devuelve true si un número es primo, false en otro caso.
function _isPrime(n) {
    if (n < 2) return false;
    var m = Math.sqrt(n);
    for (var i = 2; i <= m; i++)
        if (n % i == 0) return false;
    return true;
}
Si ejecutamos este código con valores elevados, vemos que nuestra página no sufre ningún tipo de efecto secundario mejorando considerablemente la experiencia del usuario en la misma.

Otra cuestion a tener en cuenta en los Web Workers es a que tienen y a que no tienen acceso (por cuestiones de seguridad). Bien, los Web Wokers puede acceder a
  • Objeto navigator
  • Objeto location (de solo lectura)
  • XMLHttpRequest
  • setTimeout()/clearTimeout() y setInterval()/clearInterval()
  • Caché de la aplicación
  • Importación de secuencias de comandos externas a través del método importScripts()
  • Generación de otros Web Workers
Y no pueden acceder
  • DOM (no es seguro para el subproceso)
  • Objeto window
  • Objeto document
  • Objeto parent
Respecto a los navegadores compatibles, con IE solo podremos usar esta característica de HTML5 a partir de la versión 10. Para el resto de navegadores lo mejor es mirar en esta página.

Más información en la página de w3schools 

EDIT: Por recomendación @ydarias, añado la página de Mozilla sobre el uso de los web workers como lectura recomendada.

También se puede bajar el ejemplo que hemos comentado desde aquí.

Happy coding!

No hay comentarios:

Publicar un comentario