Usando Web Workers
Los Web Workers dedicados proveen un medio sencillo para que el contenido web ejecute scripts en hilos en segundo plano. Una vez creado, un worker puede enviar mensajes a la tarea creada mediante envio de mensajes al manejador de eventos especificado por el creador. Sin embargo, los workers trabajan dentro de un contexto global diferente de la ventana actual (usar el atajo window
en lugar de self
con el fin de obtener el scope actual dentro de un Worker
retornaría, de hecho, un error).
El hilo worker puede realizar tareas sin interferir con la interfaz de usuario. Ademas, pueden realizar I/O usando XMLHttpRequest
(aunque el responseXML y los atributos channel son siempre null).
Para documentacion de referencia acerca de workers busca Worker
; este articulo complementa ese ofreciendo ejemplos y detalles adicionales. Para una lista de las funciones disponibles sobre workers, visita Functions and interfaces available to workers.
Acerca de seguridad de hilos
La interfaz Worker
crea hilos a nivel de SO reales, y la concurrencia puede causar effectos interesantes en tu código si no eres cuidadoso. Sin embargo, en el caso de los web workers, el control cuidadoso de los puntos de comunicacion con otros hilos indica que es realmente muy dificil causar problemas de concurrencia. No existe acceso a componentes no-hilo seguros o al DOM y debes pasar la informacion entrante o saliente del hilo a traves de objetos serializados. Así que debes poner esfuerzo para causar problemas en tu código.
Creando un web worker
Crear un nuevo worker es simple. Sólo tienes que llamar el constructor Worker()
, especificando la URI de un script a ejecutar en el hilo del worker (worker thread), y, si deseas poder recibir notificaciones del worker, establece la propiedad Worker.onmessage
del worker a una función de manejo de eventos apropiada.
var myWorker = new Worker("my_task.js");
myWorker.onmessage = function (oEvent) {
console.log("Called back by the worker!\n");
};
Alternativamente, puedes usar addEventListener()
:
var myWorker = new Worker("my_task.js");
myWorker.addEventListener(
"message",
function (oEvent) {
console.log("Called back by the worker!\n");
},
false,
);
myWorker.postMessage(""); // start the worker.
La Línea 1 en este ejemplo crea un nuevo worker (worker thread). La Línea 3 configura un manejador de eventos (listener) para encargarse de los eventos message
del worker. Este manejador de eventos se llamará cuando el worker llame a su propia función Worker.postMessage()
. Finalmente, la Linea 7 inicia el worker (worker thread).
Nota:
La URI pasada como parámetro del constructor de Worker
debe obedecer la política same-origin policy . Actualmente hay desacuerdo entre los desarolladores de navegadores sobre qué URIs son del mismo origen; Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0 / SeaMonkey 2.7) y posteriores sí permiten data URIs e Internet Explorer 10 no permite Blob URIs como un script válido para los workers.
Pasando datos
Los datos pasan entre la página principal y los workers son copiados, no compartidos. Los objetos se serializan a medida que se entregan al worker, y posteriormente, se deserializan en el otro extremo. La página y el worker no comparten la misma instancia, por lo que el resultado final es que un duplicado es creado en cada extremo. La mayoría de los navegadores implementan esta característica como structured cloning.
Antes de continuar, vamos a crear con fines didácticos una función llamada emulateMessage()
que simulará el comportamiento de un valor el cual es clonado y no compartido durante el paso desde un worker a la página principal o viceversa:
function emulateMessage(vVal) {
return eval("(" + JSON.stringify(vVal) + ")");
}
// Tests
// test #1
var example1 = new Number(3);
alert(typeof example1); // object
alert(typeof emulateMessage(example1)); // number
// test #2
var example2 = true;
alert(typeof example2); // boolean
alert(typeof emulateMessage(example2)); // boolean
// test #3
var example3 = new String("Hello World");
alert(typeof example3); // object
alert(typeof emulateMessage(example3)); // string
// test #4
var example4 = {
name: "John Smith",
age: 43,
};
alert(typeof example4); // object
alert(typeof emulateMessage(example4)); // object
// test #5
function Animal(sType, nAge) {
this.type = sType;
this.age = nAge;
}
var example5 = new Animal("Cat", 3);
alert(example5.constructor); // Animal
alert(emulateMessage(example5).constructor); // Object
A Un valor que es clonado y no compartido se denomina mensaje. De vuelta con los workers, los mensajes pueden ser enviados hacia y desde el hilo principal empleando postMessage()
. Los eventos de mensaje
data
atributo contienen datos devueltos desde el worker.
example.html: (la página principal):
var myWorker = new Worker("my_task.js");
myWorker.onmessage = function (oEvent) {
console.log("Worker said : " + oEvent.data);
};
myWorker.postMessage("ali");
my_task.js (el worker):
postMessage("I'm working before postMessage('ali').");
onmessage = function (oEvent) {
postMessage("Hi " + oEvent.data);
};
Nota: Como siempre, los hilos en segundo plano -incluyendo workers- no pueden manipular el DOM. Si acciones tomadas por el hilo en segundo planos resultarían en cambios en el DOM, deberian enviar mensajes a sus creadores para llevarlos a cabo.
The structured cloning algorithm can accept JSON and a few things that JSON can't like circular references.
Ejemplos pasando datos
Example 1: Crear un "eval() asíncrono
" genérico
El siguiente ejemplo muestra como usar un worker para ejecutar asíncronamente cualquier tipo de código en Javascript a traves de eval
dentro del worker:
// Syntax: asyncEval(code[, listener])
var asyncEval = (function () {
var aListeners = [],
oParser = new Worker(
"data:text/javascript;charset=US-ASCII,onmessage%20%3D%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D",
);
oParser.onmessage = function (oEvent) {
if (aListeners[oEvent.data.id]) {
aListeners[oEvent.data.id](oEvent.data.evaluated);
}
delete aListeners[oEvent.data.id];
};
return function (sCode, fListener) {
aListeners.push(fListener || null);
oParser.postMessage({
id: aListeners.length - 1,
code: sCode,
});
};
})();
Ejemplo de uso:
// asynchronous alert message...
asyncEval("3 + 2", function (sMessage) {
alert("3 + 2 = " + sMessage);
});
// asynchronous print message...
asyncEval('"Hello World!!!"', function (sHTML) {
document.body.appendChild(document.createTextNode(sHTML));
});
// asynchronous void...
asyncEval(
'(function () {\n\tvar oReq = new XMLHttpRequest();\n\toReq.open("get", "http://www.mozilla.org/", false);\n\toReq.send(null);\n\treturn oReq.responseText;\n})()',
);
Ejemplo 2: Paso avanzado de JSON Data y creación de un sistema de conmutación
Si tiene que pasar datos complejos y tienes que llamar a muchas funciones diferentes tanto en la página principal como en el Worker, puede crear un sistema como el siguiente.
example.html (the main page):
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>MDN Example - Queryable worker</title>
<script type="text/javascript">
/*
QueryableWorker instances methods:
* sendQuery(queryable function name, argument to pass 1, argument to pass 2, etc. etc): calls a Worker's queryable function
* postMessage(string or JSON Data): see Worker.prototype.postMessage()
* terminate(): terminates the Worker
* addListener(name, function): adds a listener
* removeListener(name): removes a listener
QueryableWorker instances properties:
* defaultListener: the default listener executed only when the Worker calls the postMessage() function directly
*/
function QueryableWorker(sURL, fDefListener, fOnError) {
var oInstance = this,
oWorker = new Worker(sURL),
oListeners = {};
this.defaultListener = fDefListener || function () {};
oWorker.onmessage = function (oEvent) {
if (
oEvent.data instanceof Object &&
oEvent.data.hasOwnProperty("vo42t30") &&
oEvent.data.hasOwnProperty("rnb93qh")
) {
oListeners[oEvent.data.vo42t30].apply(
oInstance,
oEvent.data.rnb93qh,
);
} else {
this.defaultListener.call(oInstance, oEvent.data);
}
};
if (fOnError) {
oWorker.onerror = fOnError;
}
this.sendQuery =
function (/* queryable function name, argument to pass 1, argument to pass 2, etc. etc */) {
if (arguments.length < 1) {
throw new TypeError(
"QueryableWorker.sendQuery - not enough arguments",
);
return;
}
oWorker.postMessage({
bk4e1h0: arguments[0],
ktp3fm1: Array.prototype.slice.call(arguments, 1),
});
};
this.postMessage = function (vMsg) {
//I just think there is no need to use call() method
//how about just oWorker.postMessage(vMsg);
//the same situation with terminate
//well,just a little faster,no search up the prototye chain
Worker.prototype.postMessage.call(oWorker, vMsg);
};
this.terminate = function () {
Worker.prototype.terminate.call(oWorker);
};
this.addListener = function (sName, fListener) {
oListeners[sName] = fListener;
};
this.removeListener = function (sName) {
delete oListeners[sName];
};
}
// your custom "queryable" worker
var oMyTask = new QueryableWorker(
"my_task.js" /* , yourDefaultMessageListenerHere [optional], yourErrorListenerHere [optional] */,
);
// your custom "listeners"
oMyTask.addListener("printSomething", function (nResult) {
document
.getElementById("firstLink")
.parentNode.appendChild(
document.createTextNode(" The difference is " + nResult + "!"),
);
});
oMyTask.addListener("alertSomething", function (nDeltaT, sUnit) {
alert("Worker waited for " + nDeltaT + " " + sUnit + " :-)");
});
</script>
</head>
<body>
<ul>
<li>
<a
id="firstLink"
href="javascript:oMyTask.sendQuery('getDifference', 5, 3);"
>What is the difference between 5 and 3?</a
>
</li>
<li>
<a href="javascript:oMyTask.sendQuery('waitSomething');"
>Wait 3 seconds</a
>
</li>
<li>
<a href="javascript:oMyTask.terminate();">terminate() the Worker</a>
</li>
</ul>
</body>
</html>
my_task.js (el worker):
// your custom PRIVATE functions
function myPrivateFunc1() {
// do something
}
function myPrivateFunc2() {
// do something
}
// etc. etc.
// your custom PUBLIC functions (i.e. queryable from the main page)
var queryableFunctions = {
// example #1: get the difference between two numbers:
getDifference: function (nMinuend, nSubtrahend) {
reply("printSomething", nMinuend - nSubtrahend);
},
// example #2: wait three seconds
waitSomething: function () {
setTimeout(function () {
reply("alertSomething", 3, "seconds");
}, 3000);
},
};
// system functions
function defaultQuery(vMsg) {
// your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
// do something
}
function reply(/* listener name, argument to pass 1, argument to pass 2, etc. etc */) {
if (arguments.length < 1) {
throw new TypeError("reply - not enough arguments");
return;
}
postMessage({
vo42t30: arguments[0],
rnb93qh: Array.prototype.slice.call(arguments, 1),
});
}
onmessage = function (oEvent) {
if (
oEvent.data instanceof Object &&
oEvent.data.hasOwnProperty("bk4e1h0") &&
oEvent.data.hasOwnProperty("ktp3fm1")
) {
queryableFunctions[oEvent.data.bk4e1h0].apply(self, oEvent.data.ktp3fm1);
} else {
defaultQuery(oEvent.data);
}
};
Es un método posible para conmutar el contenido de cada mensaje de cada mainpage-worker y viceversa.
Pasando datos mediante transferencia de propiedades (objetos transferibles)
Google Chrome 17 y Firefox 18 implementan un método adicional para enviar ciertos tipos de objetos desde o hacia el worker con un mejor rendimiento. Estos objetos se denominan objetos transferibles (transferable objects), es decir, objetos que implementan la interfaz Transferable
. Los objetos transferibles se transfieren de un contexto a otro con una operación "zero-copy". Esto supone una gran mejora de rendimiento al enviar grandes cantidades de datos. Piensa en ello como un paso por referencia si vienes del mundo de C/C++. Sin embargo, a diferecia del paso por referencia, la "versión" original no queda disponible una vez transferida. Su contenido es transferido al nuevo contexto. Por ejemplo, cuando se transfiere un ArrayBuffer
de tu aplicacion al Worker, el contenido del ArrayBuffer
original se vacía y no se puede utilizar posteriormente. Su contenido es (literalmente) transferido al contexto del Worker.
// Create a 32MB "file" and fill it.
var uInt8Array = new Uint8Array(1024 * 1024 * 32); // 32MB
for (var i = 0; i < uInt8Array.length; ++i) {
uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
Para más información sobre los objetos transferibles, visita HTML5Rocks .
Spawning subworkers
Workers may spawn more workers if they wish. So-called subworkers must be hosted within the same origin as the parent page. Also, the URIs for subworkers are resolved relative to the parent worker's location rather than that of the owning page. This makes it easier for workers to keep track of where their dependencies are.
Subworkers are currently not supported in Chrome. See crbug.com/31666 .
Embedded workers
There is not an "official" way to embed the code of a worker within a web page as for the <script>
elements. But a <script>
element which does not have a src
attribute and has a type
attribute that does not identify an executable mime-type will be considered a data block element, that JavaScript could use. "Data blocks" is a more general feature of HTML5 that can carry almost any textual data. So, a worker could be embedded in this way:
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>MDN Example - Embedded worker</title>
<script type="text/js-worker">
// This script WON'T be parsed by JS engines because its mime-type is text/js-worker.
var myVar = "Hello World!";
// Rest of your worker code goes here.
</script>
<script type="text/javascript">
// This script WILL be parsed by JS engines because its mime-type is text/javascript.
function pageLog(sMsg) {
// Use a fragment: browser will only render/reflow once.
var oFragm = document.createDocumentFragment();
oFragm.appendChild(document.createTextNode(sMsg));
oFragm.appendChild(document.createElement("br"));
document.querySelector("#logDisplay").appendChild(oFragm);
}
</script>
<script type="text/js-worker">
// This script WON'T be parsed by JS engines because its mime-type is text/js-worker.
onmessage = function (oEvent) {
postMessage(myVar);
};
// Rest of your worker code goes here.
</script>
<script type="text/javascript">
// This script WILL be parsed by JS engines because its mime-type is text/javascript.
// In the past...:
// blob builder existed
// ...but now we use Blob...:
var blob = new Blob(
Array.prototype.map.call(
document.querySelectorAll('script[type="text\/js-worker"]'),
function (oScript) {
return oScript.textContent;
},
),
{ type: "text/javascript" },
);
// Creating a new document.worker property containing all our "text/js-worker" scripts.
document.worker = new Worker(window.URL.createObjectURL(blob));
document.worker.onmessage = function (oEvent) {
pageLog("Received: " + oEvent.data);
};
// Start the worker.
window.onload = function () {
document.worker.postMessage("");
};
</script>
</head>
<body>
<div id="logDisplay"></div>
</body>
</html>
The embedded worker is now nested into a new custom document.worker
property.
Tiempos fuera e intervalos
Los trabajadores pueden usar tiempos fuera e intervalos de la misma forma que el "hilo principal". Esto puede ser útil, por ejemplo, si quieres tener a tu hilo trabajador corriendo codigo periodicamente en lugar de sin parar.
Ver setTimeout()
, clearTimeout()
, setInterval()
, y clearInterval()
para más detalles. Ver también: JavaScript Timers.
Terminating a worker
If you need to immediately terminate a running worker, you can do so by calling the worker's terminate()
method:
myWorker.terminate();
The worker thread is killed immediately without an opportunity to complete its operations or clean up after itself.
Workers may close themselves by calling their own nsIWorkerScope.close()
method:
self.close();
Manejo de errores
When a runtime error occurs in worker, its onerror
event handler is called. It receives an event named error
which implements the ErrorEvent
interface. The event doesn't bubble and is cancelable; to prevent the default action from taking place, the worker can call the error event's preventDefault()
method.
The error event has the following three fields that are of interest:
Accediendo al objeto navigator
Los workers pueden acceder al objeto navigator
, el cuál está disponible dentro de su scope actual. Este contiene los siguientes strings que pueden ser usados para identificar el navegador, al igual que puede realizarse usando scripts normales:
appName
appVersion
platform
userAgent
Importing scripts and libraries
Worker threads have access to a global function, importScripts()
, which lets them import scripts or libraries into their scope. It accepts as parameters zero or more URIs to resources to import; all of the following examples are valid:
importScripts(); /* imports nothing */
importScripts("foo.js"); /* imports just "foo.js" */
importScripts("foo.js", "bar.js"); /* imports two scripts */
The browser loads each listed script and executes it. Any global objects from each script may then be used by the worker. If the script can't be loaded, NETWORK_ERROR
is thrown, and subsequent code will not be executed. Previously executed code (including code deferred using window.setTimeout()
) will still be functional though. Function declarations after the importScripts()
method are also kept, since these are always evaluated before the rest of the code.
Nota:
Scripts may be downloaded in any order, but will be executed in the order in which you pass the filenames into importScripts()
. This is done synchronously; importScripts()
does not return until all the scripts have been loaded and executed.
Examples
This section provides several examples of how to use DOM workers.
Performing computations in the background
One way workers are useful is to allow your code to perform processor-intensive calculations without blocking the user interface thread. In this example, a worker is used to calculate Fibonacci numbers.
The JavaScript code
The following JavaScript code is stored in the "fibonacci.js" file referenced by the HTML in the next section.
var results = [];
function resultReceiver(event) {
results.push(parseInt(event.data));
if (results.length == 2) {
postMessage(results[0] + results[1]);
}
}
function errorReceiver(event) {
throw event.data;
}
onmessage = function (event) {
var n = parseInt(event.data);
if (n == 0 || n == 1) {
postMessage(n);
return;
}
for (var i = 1; i <= 2; i++) {
var worker = new Worker("fibonacci.js");
worker.onmessage = resultReceiver;
worker.onerror = errorReceiver;
worker.postMessage(n - i);
}
};
The worker sets the property onmessage
to a function which will receive messages sent when the worker object's postMessage()
is called. (Note that this differs from defining a global variable of that name, or defining a function with that name. var onmessage
and function onmessage
will define global properties with those names, but they will not register the function to receive messages sent by the web page that created the worker.) This starts the recursion, spawning new copies of itself to handle each iteration of the calculation.
The HTML code
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Test threads fibonacci</title>
</head>
<body>
<div id="result"></div>
<script language="javascript">
var worker = new Worker("fibonacci.js");
worker.onmessage = function (event) {
document.getElementById("result").textContent = event.data;
dump("Got: " + event.data + "\n");
};
worker.onerror = function (error) {
dump("Worker error: " + error.message + "\n");
throw error;
};
worker.postMessage("5");
</script>
</body>
</html>
The web page creates a div
element with the ID result
, which gets used to display the result, then spawns the worker. After spawning the worker, the onmessage
handler is configured to display the results by setting the contents of the div
element, and the onerror
handler is set to dump the error message.
Finally, a message is sent to the worker to start it.
Performing web I/O in the background
You can find an example of this in the article Using workers in extensions .
Dividing tasks among multiple workers
As multi-core computers become increasingly common, it's often useful to divide computationally complex tasks among multiple workers, which may then perform those tasks on multiple-processor cores.
example coming soon
Creating workers from within workers
The Fibonacci example shown previously demonstrates that workers can in fact spawn additional workers. This makes it easy to create recursive routines.