Traducción en curso
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.
Alternativamente, puedes usar addEventListener()
:
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).
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: (the main page):
var myWorker = new Worker("my_task.js"); myWorker.onmessage = function (oEvent) { console.log("Worker said : " + oEvent.data); }; myWorker.postMessage("ali");
my_task.js (the worker):
postMessage("I\'m working before postMessage(\'ali\')."); onmessage = function (oEvent) { postMessage("Hi " + oEvent.data); };
The structured cloning algorithm can accept JSON and a few things that JSON can't like circular references.
Ejemplos pasando datos
Example #1: Create a generic "asynchronous eval()
"
The following example shows how to use a worker in order to execute asynchronously any kind of JavaScript code through eval()
within the 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 }); }; })();
Sample usage:
// 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\", \"https://www.mozilla.org/\", false);\n\toReq.send(null);\n\treturn oReq.responseText;\n})()");
Example #2: Advanced passing JSON Data and creating a switching system
If you have to pass some complex data and have to call many different functions both in the main page and in the Worker, you can create a system like the following.
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 (the 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); } };
It is a possible method to switch the content of each mainpage-worker – and vice versa – message.
Pasando datos mediante transferencia de propiedades (objetos transferibles)
Google Chrome 17 and Firefox 18 contain an additional way to pass certain kind of objects (Transferable objects, that is objects implementing the Transferable
interface) to or from a worker with high performance. Transferable objects are transferred from one context to another with a zero-copy operation. This means a vast performance improvement when sending large data. Think of it as pass-by-reference if you're from the C/C++ world. However, unlike pass-by-reference, the 'version' from the calling context is no longer available once transferred. Its ownership is transferred to the new context. For example, when transferring an ArrayBuffer
from your main app to Worker, the original ArrayBuffer
is cleared and no longer usable. Its content is (quite literally) transferred to the Worker context.
// 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]);
For more information on transferable objects, see 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.
Timeouts and intervals
Workers can use timeouts and intervals just like the main thread can. This can be useful, for example, if you want to have your worker thread run code periodically instead of nonstop.
See setTimeout()
, clearTimeout()
, setInterval()
, and clearInterval()
for details. See also: 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();
Handling errors
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:
message
- A human-readable error message.
filename
- The name of the script file in which the error occurred.
lineno
- The line number of the script file on which the error occurred.
Accessing the navigator object
Workers may access the navigator
object, which is available within their scope. It contains the following strings which can be used to identify the browser, just as can be done from normal scripts:
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.
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.
Browser compatibility
Feature | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari (WebKit) |
---|---|---|---|---|---|
Dedicated workers | 3 | 3.5 (1.9.1) | 10 | 10.60 | 4 |
Shared workers | 3 | No support | No support | 10.60 | 5 |
Passing data using structured cloning . | 13 | 8 (8) | 10 | 11.50 | 5.1 |
Passing data using transferable objects | 17 webkit 21 |
18 (18) | No support | 15 | 6 |
Global URL |
10 as webkitURL 23 |
21 (21) | 11 | 15 | 6 as webkitURL |
Feature | Android | Chrome for Android | Firefox Mobile (Gecko) | IE Phone | Opera Mobile | Safari Mobile |
---|---|---|---|---|---|---|
Dedicated workers | --- | 0.16 | 3.5 (1.9.1) | --- | 11 | 5 |
Shared workers | --- | No support | --- | --- | --- | --- |
Passing data using structured cloning . | --- | 0.16 | 8 | --- | --- | --- |
Passing data using transferable objects | --- | 18 | --- | --- | --- |