Webworkers
Los WebWorkers nos permiten ejecutar procesos en paralelo sin impactar en el rendimiento del navegador.
Sin Web Workers por ejemplo, las tareas JavaScript pueden bloquear otros JavaScript de la página y por tanto el Navegador se puede colgar.
JavaScript es un entorno de subproceso único, es decir, que no se pueden ejecutar varias secuencias de comandos al mismo tiempo. Si tenemos que manipular eventos, manejar grandes cantidades de datos y manipular objetos DOM, no podemos hacerlo de forma simultánea debido a las limitaciones de JavaScript. La ejecución de comandos se realiza en un único subproceso.
Con Web Workers:
- Podemos realizar procesos background
- Podemos ejecutar procesos de forma paralela
Los Web Workers permiten realizar acciones con tiempos de ejecución muy largos para gestionar tareas pesadas en segundo plano, pero sin bloquear la interfaz de usuario.
Podemos usarlos para multitud de operaciones que en la actualidad consumirían recursos de navegación.
Tienen algunas limitaciones, ya que se ejecutan en una SANDBOX que no permite acceder a todos los recursos disponibles a los JavaScript normales.
- No se puede acceder al objet window (window.document)
- No hay acceso directo a la página WEB ni al DOM API
- Se puede usar las funciones de timing
- Tienen otras limitaciones de acceso.
- Debemos comunicarnos con ellos a través de mensajes.
Para trabajar con WebWorkers, lo primero es comprobar que tenemos soporte para ellos.
Ejemplo
//Comprobar que nuestro navegador soporta WebWorkers. if (typeof(Worker) !== "undefined") { document.getElementById("support").innerHTML = “ A tu navegador no le gustan los WebWorkers”; }
Crear un WebWorker
Los Web Workers se ejecutan en un subproceso aislado. Por tanto, es necesario que el código que ejecutan se encuentre en un archivo independiente.
Sin embargo, antes de hacer esto, lo primero que hay que hacer es crear un nuevo objeto Worker en la página principal. El constructor debe tener el nombre de la secuencia de comandos del Worker.
Los pasos para crear un WebWorker son los siguientes:
- Creamos un objeto WebWorker y le pasamos el fichero JavaScript a ejecutar. Si el archivo especificado existe, el navegador generará un nuevo subproceso que lo descargará de forma asíncrona. El Worker no empezará a trabajar hasta que el archivo se haya descargado al completo.
var worker = new Worker('fichero.js');
- Lo arrancamos con el método PostMessage
worker.postMessage();
Trabajar con un WebWorker
El trabajo entre un Worker y su programa principal se hace mediante eventos. Se usa el método postMessage().
Este método hacepta normalmente una cadena de caracteres, aunque muchos Navegadores modernos soportan la posibilidad de incluir in objeto JSON.
En el programa principal debemos activar un Listener que responda al evento “message” y que capture lo que pueda llegar desde el worker. Por ejemplo:
worker.addEventListener('message', f1(e) { console.log('Respuesta del Worker: ', e.data); }, false);
Como vemos se puede acceder a los datos del evento a través del parámetro de la función f1. En este caso nos limitamos a sacarlo por el log.
Por otro lado, en el worker debemos incluir el Listener para que esté pendiente de recepcionar los mensajes que le llegan y responder a ellos. Por ejemplo algo como el siguiente código
self.addEventListener('message', f1_worker(e) { self.postMessage(e.data); }, false);
Los mensajes que van del programa principal a los Workers y viceversa no son compartidos, en realidad se copias. Los objetos transferidos se serializan y des-serializan en cada fase del proceso.
Por lo tanto, el Worker no comparte la misma instancia que el programa principal, por lo que el resultado final es la creación de un duplicado en cada transferencia.
Para parar el woker usamos dos posibles opciones:
- worker.terminate() desde la página principal
- self.close()dentro del propio Worker.
El siguiente ejemplo muestra el ciclo de vida de una llamada a un WebWorker desde un programa principal.
//Crear un nuevo worker w1 = new Worker(“trabajo_worker.js"); //Mandar un mensaje al worker w1.postMessage(“Empieza a currar hombre”); //Añadir un event listener w1.addEventListener("message", messageHandler, true); //Procesar mensajes entrantes function messageHandler(e) { // procesamos mensajes del worker } //Gestión de errores w1.addEventListener("error", errorHandler, true); //Parar el worker w1.terminate();
El siguiente ejemplo muestra un programa principal que llama a un webworker que devuelve el dato que se le pasa en mayúsculas. Programa principal:
<script> //Crear un nuevo worker w1 = new Worker("trabajo_worker.js"); //Mandar un mensaje al worker w1.postMessage("pedro"); //Añadir un event listener w1.addEventListener("message", m1, true); //Procesar mensajes entrantes function m1(e) { // procesamos mensajes del worker console.log('Ese nombre en mayúsculas es: ', e.data); } //Gestión de errores w1.addEventListener("error", errorHandler, true); //Parar el worker w1.terminate(); </script>
El werworker sería el siguiente código:
self.addEventListener('message', f1(e) { self.postMessage(e.data.toUpperCase()); }, false);
Otros ejemplos de uso. Se puede invocar a un worker dentro de otro worker, es decir podemos tener subworkers. A la hora de utilizar los Subworkers es necesario tener en cuenta los siguientes aspectos:
- Los Subworkers deben situados en el mismo origen que la página principal.
- La resolución de las URI de los Subworkers está relacionada con la ubicación de su Worker principal.
//Trabajar con el Worker function messageHandler(e) { postMessage(“mensaje del worker: ” + e.data); } addEventListener(“message”, messageHandler, true); //Podemos llamar a un worker dentro de otro worker var sub1 = new Worker(“subWorker.js”);
Gestión de errores
Al igual que cualquier programa JavaScript, es necesario gestionar los errores que se producen en los Web Workers.
Podemos hacerlo controlando el evento ErrorEvent. La interfaz incluye tres propiedades útiles para descubrir la causa del error:
- Filename: el nombre del fichero Worker que causó el error
- Lineno: el número de línea donde se produjo el error
- Message: descripción del error
A continuación, se muestra un ejemplo sobre cómo configurar un gestor de eventos onerror para controlar el error.
<output id=”error” style=”color: red;”></output> <output id=”result”></output> <script> function onError(e) { document.getElementById(‘error’).textContent = [ 'ERROR: Línea ', e.lineno, ' en ', e.filename, ': ', e.message].join(”); } function onMsg(e) { document.getElementById(‘result’).textContent = e.data; } var worker = new Worker(‘workerError.js’); worker.addEventListener(‘message’, onMsg, false); worker.addEventListener(‘error’, onError, false); worker.postMessage(); // Arrancar el worker sin un mensaje. </script>
El worker asociado sería el siguiente:
self.addEventListener('message', function(e) { postMessage(1/x); };
El resultado sería aparecido al siguiente
ERROR: Línea 2 en workerError.js: Uncaught ReferenceError: x is not defined
Websocket
Las aplicaciones actuales demandan una latencia cercana a 0. También necesitan comunicación bidireccional. En principio, la funcionalidad actual está basada en la arquitectura request/response de HTTP.
Por ejemplo, un cliente carga una página WEB y no sucede nada hasta que el usuario pulsa algo para pasar a la página siguiente. La incorporación de AJAX permitió disponer de páginas más dinámicas. De todas formas es necesario interactuar y cargar nuevos datos del servidor de forma periódica.
HTTP es half-duplex, de forma que la información de Header se manda en cada petición y respuesta HTTP lo que produce una sobrecarga indeseada
Si observamos un request HTTP típico
GET /PollingStock//PollingStock HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost:8080/PollingStock/ Cookie: showInheritedConstant=false; showInheritedProtectedConstant=false; showInheritedProperty=false; showInheritedProtectedProperty=false; showInheritedMethod=false; showInheritedProtectedMethod=false; showInheritedEvent=false; showInheritedStyle=false; showInheritedEffect=false;
Y un response
HTTP/1.x 200 OK X-Powered-By: Servlet/2.5 Server: Sun Java System Application Server 9.1_02 Content-Type: text/html;charset=UTF-8 Content-Length: 321 Date: Sat, 07 Nov 2009 00:32:46 GMT
El Overhead innecesario en este ejemplo alcanza 871 bytes. Puede ser mayor y lo peor es que no escala.
WebSocket define una API para establecer conexiones de tipo “socket” entre un navegador y el servidor. Por tanto existe una conexión persistente entre el cliente y el servidor y ambas partes pueden enviar datos en cualquier momento.
Algunos ejemplos de su uso pueden ser las siguientes:
- Aplicaciones financieras
- Sociales
- Juegos online
- Otras
El websocket tiene las siguientes características
- Protocolos W3C API y IETF Protocol
- Full-duplex, single socket
- Comunican las páginas con un Servidor remoto
- Atraviesa firewalls, proxies, y routers
- Comparte el puerto con el HTTP
- Reduce de forma drástica el overhead
La conexión se establece a través del upgrade del protocolo HTTP al protocolo WebSocket usando la misma conexión
Una vez actualizado, los data frames de WebSocket se pueden enviar de un lado a otro entre el cliente y el servidor en modo full-duplex.
Las Frames se pueden enviar en modo full-duplex en cualquier dirección y en cualquier momento.
Creación de un websocket
Como siempre, lo primero que debemos averiguar es si disponemos de soporte para WebSocket
//Checking for browser support if (window.WebSocket) { document.getElementById("support").innerHTML = "HTML5 WebSocket is supported"; } else { document.getElementById("support").innerHTML = "HTML5 WebSocket is not supported"; }
Se puede crear una conexión Websocket llamando al constructor correspondiente:
var con1 = new WebSocket('ws://ejemplo.curso.com', ['soap']);
Se usa el esquema “ws” para conexiones de este tipo. También podemos observar los subprotocolos que se han asignado a la conexión que debe ser uno de los registrados y permitidos por el estándar. Ahora mismo solo se soporta “soap”.
Podemos usar distintos eventos para controlar la gestión de la conexión:
- Onopen: cuando se establece la conexión
- Onerror: cuando se produce un error
- Onmessage: cuando se recibe un mensaje del servidor
Algunos ejemplos:
Con1.onerror = f1(error) { console.log('Se ha producido el error:' + error); }; Con1.onmessage = f1 (e) { console.log('Mensaje del servidor: ' + e.data); };
Trabajar con WEBSOCKET
Una vez establecida la conexión con el servidor, podemos enviar un mensaje mediante el método send. Tiene el siguiente formato:
conexion.send('your message');
Aunque por ahora solo se soportan cadenas, algunos navegadores están empezando a trabajar con objetos lo que permite una mayor versatilidad.
En el siguiente ejemplo vemos como mandar un mensaje cuando hemos establecido la conexión:
Con1.onopen = f1 () { Con1.send('Esto es una prueba'); // Mandar el mensaje };
Por supuesto, el servidor puede también mandar mensajes. Para recibirlos habilitamos el método callbak “onmessage” como hemos visto en el punto anterior. Este método recibe un objeto de tipo evento con los datos asociados.
Un ejemplo completo de gestión de websocket
//Crear WebSocket var sock1 = new WebSocket("ws://www.websocket.org”); // Asociar un Listener Sock1.onopen = function(evt) { alert("Conexión abierta…”); }; //Capturar mensajes del ervisor Sock1.onmessage = function(evt) { alert(“Received message: “ + evt.data); }; //Al cerrar la conexión Sock1.onclose = function(evt) { alert(“Conexión cerrada…”); }; // Mandar datos Sock1.send(“Saludos!”); //Cerramos el WebSocket Sock1.close();