I have a web worker specified as such below:
var w;
function startWorker() {
if(typeof(Worker) !== "undefined") {
if(typeof(w) == "undefined") {
w = new Worker("scripts/gameengine.js");
}
w.onmessage = function(event) {
console.log("Testing web worker");
};
} else {
console.log("No support for web workers");
}
}
function stopWorker() {
w.terminate();
w = undefined;
}
However; when I run startWorker(); from an onClick function I get the error:
Uncaught TypeError: undefined is not a function on line 117
line 117 is w.terminate(); in my stopWorker.
Any ideas?
If the click handler fires twice, w will be undefined on the second invocation.
Defensive programming is advisable, for these reasons:
The code should only call things that are well-defined; otherwise JS tends to block execution of later statements
Properly handled, the console.log can be used to tell you what is happening
function stopWorker() {
if ( w && (typeof(w.terminate)==='function') ){
w.terminate();
w = undefined;
console.log('terminated worker per click request');
} else {
console.log('received click request to terminate worker, but worker undefined');
}
}
Also, be aware that the last time I checked, workers could not access browser functionality, and could only do message passing back to the main script. That means no jQuery, no event handling, no trying to write to objects on the screen, etc. Just computing and message passing.
Related
I am connecting to a web socket server from my web page, and sending click events to the server through this.
In my click routine, I am doing
if (socket.readyState === WebSocket.OPEN) {
//send
}else {
msgsArray.push(messages)
}
Then in socket.onOpen, I post all the messages from msgsArray using shift();
But there seems to be a possible race condition, if the socket is opened after I've check that the web socket isn't open but before I've added the message to the array.
Is there actually no race condition because JavaScript is single-threaded? If not, is there any way for me to make this "thread"-safe? Is it guaranteed that onOpen will only be called after my click event processing is finished?
--- Update ---
The race condition I am referring to is where socket.readyState is not OPEN where I'm checking it, but it becomes OPEN and socket.onopen is called before I add the message to the array. But actually I don't think that can happen, can it? onopen can't be called before the click routine finishes up, right? But what about the opposite, where socket.readyState is OPEN, but closes right as I am writing to it? I will add try catch around that and add it to the array in the catch block. I think this should handle all possible situations. If not, can you advise?
After reading the documentation on WebSocket.send, I updated my code to be completely "race-safe"
//Obj.socketMessagesToSend is a JS array ([])
if (Obj.socket && Obj.socket.readyState === WebSocket.OPEN) {
try {
Obj.socket.send(JSON.stringify(report));
} catch (e) {
Obj.socketMessagesToSend.push(JSON.stringify(report));
Obj.connectToWebSocket();
}
} else {
Obj.socketMessagesToSend.push(JSON.stringify(report));
Obj.connectToWebSocket();
}
And
Obj.connectToWebSocket() = function() {
if (Obj.socket && (Obj.socket.readyState === WebSocket.CONNECTING || Obj.socket.readyState === WebSocket.OPEN)) return;
if (typeof Config.WebSocketInfo != 'object') return;
if (!Config.WebSocketInfo.enabled) return;
try {
Obj.socket = new WebSocket(Config.WebSocketInfo.url);
Obj.socket.onmessage = Obj.socketOnMessage;
Obj.socket.onopen = function(e) {
// console.log("Web Socket Connection established!");
while (Obj.socketMessagesToSend.length != 0) {
Obj.socket.send(Obj.socketMessagesToSend.shift());
}
}
} catch (e) {
console.log('Could not connect. Is connection blocked by your content-security-policy?');
}
}
I'm trying to understand this example:
HTML (main code):
<html>
<title>Test threads fibonacci</title>
<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>
Javascript (worker code):
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);
}
};
I have the following questions:
When exactly the worker code starts to run ? Immediately after the execution of var worker = new Worker("fibonacci.js"); ?
Is that true that onmessage = function(event) { ... } assignment in the worker code will be executed before worker.postMessage("5"); in the main code ?
Can worker code access global variables that are defined in the main code (like worker)?
Can main code access global variables that are defined in the worker code (like results)?
It seems to me that worker.onmessage = function(event) {...} in the main code has the same meaning like onmessage = function(event) {...} in the worker code (which is onmessage event handler of the worker). Where am I wrong ? What is the difference between them ?
What this code should actually do ? When I run it here it just prints "5". Is that what it is supposed to do, or I'm missing something ?
Thanks a lot !
Check out HTML5 Rocks: The Basics of Web Workers for general tutorial.
Workers will start as soon as you call the postMessage method of the worker.
the function bound to worker's onmessage in the main code will work when the worker calls postMessage.
global variables are not shared between main and worker threads. The only way to pass data is through messaging via postMessage.
as you suspected, the onmessage on both worker and main code has the same meaning. It is an event handler for when the thread receives a message event. You can even use addEventListener instead, catching message event:
Main Code:
function showResult(event) {
document.getElementById("result").textContent = event.data;
dump("Got: " + event.data + "\n");
}
var worker = new Worker("fibonacci.js");
worker.addEventListener('message', showResult, false);
Worker code:
addEventListener('message', resultReceiver, false);
The fibonacci example you took is a recursive worker example. If not using workers, it would be something like this:
function fibonacci(n) {
if (n == 0 || n == 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
var result = fibonacci(5);
dump("Got: " + result + "\n");
(oh no, I'm not going to do a stackless for you. You write it yourself!)
I also want to add that you can debug web workers only in Chromium based browsers. You have to select Sources in developer panel and in right column expand bottom line Workers and then choose check box pause on start.
I am using sendMessage and onMessage listener (both in background and content pages), and I am seeing some messages are getting lost. I have a few "global" variables that I store everytime I go to suspend state, and restore them among the first things when the script starts (I register the handlers first). However, given that the chrome.storage is asynchronous, I suspect message processing is happening before I get to load the global state (and hence the appearance of losing the messages).
Following is the relevant piece of code.
# Register some important listeners.
chrome.alarms.onAlarm.addListener(onAlarm);
chrome.runtime.onMessage.addListener(onMessage);
# Define global variables.
var tabIdList = new Array();
var keepAlives = new Array();
keepAlives["myTab1"] = -1;
keepAlives["myTab2"] = -1;
tabIdList["myTab1"] = -1;
tabIdList["myTab2"] = -1;
# Reload previously stored state of global variables...
reloadGlobalVariable();
# Handle received messages, anytime a message is received,
# set keepAlive for the tab that sends the message.
#
function onMessage(msg, sender) {
if (sender.tab) {
if (msg.message === "hello") {
recordNewTab(msg.tabName, sender.tab.id);
}
keepAlive(msg.tabName, sender.tab.id);
}
function recordNewTab(tabName, tabId) {
tabIdList[tabName] = tabId;
}
function keepAlive(tabName, tabId) {
if (tabIdList[tabName] == tabId) {
keepAlives[tabName] = 1;
}
}
chrome.runtime.onSuspend.addListener(function() {
storeGlobalState();
});
function onAlarm(alarm) {
for (var key in tabIdList) {
if (tabIdList[key] != -1) {
if (keepAlives[key] == -2) {
removeTabRecord(key);
} else {
--keepAlives[key];
sendMessage(key, "ping"); // content pages respond to this message
}
}
}
storeGlobalState(); // probably unnecessary.
}
How can I make sure that onAlarm only continues processing if the global variables have been reloaded?
I use chrome.storage.local.set/get which are asynchronous.
Original question to get debugging hints about suspended/wake states here...
How to debug background/event page suspended state
Well, you can't do anything about async nature of Event page processing and Chrome Storage API. And there's no "delaying until" in async JS.
Therefore, you'll need to make do with callbacks. This should work:
var globalsReady = false;
chrome.foo.onBar.addListener(handler);
function handler(a, b, c) {
restoreGlobals(function() {
/* Do actual handling using a, b, c */
});
// Special note for onMessage: if you are sending a reply asynchronously,
// you'll need to return true; here
}
function restoreGlobals(callback) {
if(!globalsReady) {
chrome.storage.local.get(/*...*/, function(data) {
/* restore globals here */
globalsReady = true;
if(typeof callback == "function") callback();
});
} else {
// Already done restoring
if(typeof callback == "function") callback();
}
}
restoreGlobals();
Something about my use of chrome.hid.send seems to be leaving the bus in a bad state. I consistently can NOT get my second usage of the API call to work. Sometimes, it will also fail on the first usage. WITH THE EXACT SAME CODE, I can come back and try a while later (maybe 10min) and the first send will work.
The device I'm working with does not return a response to all messages sent to it. The test message for example, is just a dummy message that is ignored by the device. I've tested this both on a mac and a PC. My call stack depth is 2 at this point in my application (literally first one is kicked off by a button click and then a setTimeout calls the same method 5s later).
I've testing sending buffers of length 64Bytes as well as 58Bytes. The properties from the HidDeviceInfo object read "maxInputReportSize":64,"maxOutputReportSize":64
Params on first usage:
Params on second usage:
I really can't identify how I'm using the API incorrectly. When messages do succeed, I can see them on the device side.
// Transmits the given data
//
// #param[in] outData, The data to send as an ArrayBuffer
// #param[in] onTxCompleted, The method called on completion of the outgoing transfer. The return
// code is passed as a string.
// #param[in] onRxCompleted, The method called on completion of the incoming transfer. The return
// code is passed as a string along with the response as an ArrayBuffer.
send: function(outData, onTxCompleted, onRxCompleted) {
if (-1 === connection_) {
console.log("Attempted to send data with no device connected.");
return;
}
if (0 == outData.byteLength) {
console.log("Attempted to send nothing.");
return;
}
if (COMMS.receiving) {
console.log("Waiting for a response to a previous message. Aborting.");
return;
}
if (COMMS.transmitting) {
console.log("Waiting for a previous message to finish sending. Aborting.");
return;
}
COMMS.transmitting = true;
var dummyUint8Array = new Uint8Array(outData);
chrome.hid.send(connection_, REPORT_ID, outData, function() {
COMMS.transmitting = false;
if (onTxCompleted) {
onTxCompleted(chrome.runtime.lastError ? chrome.runtime.lastError.message : '');
}
if (chrome.runtime.lastError) {
console.log('Error in COMMS.send: ' + chrome.runtime.lastError.message);
return;
}
// Register a response handler if one is expected
if (onRxCompleted) {
COMMS.receiving = true;
chrome.hid.receive(connection_, function(reportId, inData) {
COMMS.receiving = false;
onRxCompleted(chrome.runtime.lastError ? chrome.runtime.lastError.message : '', inData);
});
}
});
}
// Example usage
var testMessage = new Uint8Array(58);
var testTransmission = function() {
message[0] = 123;
COMMS.send(message.buffer, null, null);
setTimeout(testTransmission, 5000);
};
testTranmission();
The issue is that Windows requires buffers to be the full report size expected by the device. I have filed a bug against Chromium to track adding a workaround or at least a better error message to pinpoint the problem.
In general you can get more detailed error messages from the chrome.hid API by enabling verbose logging with the --enable-logging --v=1 command line options. Full documentation of Chrome logging is here.
I'm using WebSockets to connect to a remote host, and whenever I populate realData and pass it to grapher(), the JavaScript console keeps telling me realDatais undefined. I tried checking the type of the data in the array, but it seems to be fine. I've called grapher() before using an array with random data, and the call went through without any problems. With the data from the WebSocket, however, the call will always give me "error: realData is not defined". I'm not sure why this is happening. Here is the code I used:
current.html:
var command = "Hi Scott"
getData();
function getData()
{
console.log("getData is called");
if("WebSocket" in window)
{
var dataCollector = new WebSocket("ws://console.sb2.orbit-lab.org:6100",'binary');
dataCollector.binaryType = "arraybuffer";
console.log(dataCollector.readyState);
dataCollector.onopen = function()
{
//alert("The WebSocket is now open!");
console.log("Ready state in onopen is: " + dataCollector.readyState);
dataCollector.send(command);
console.log(command + " sent");
}
dataCollector.onmessage = function(evt)
{
console.log("onmessage is being called");
var realData = new Uint8Array(evt.data);
console.log(realData);
grapher(realData); //everything up to this point works perfectly.
}
dataCollector.onclose = function()
{
alert("Connection to Server has been closed");
}
return (dataCollector);
}
else
{
alert("Your browser does not support WebSockets!");
}
}
graphing.js:
function grapher(realData)
{
console.log("grapher is called");
setInterval('myGraph(realData);',1000); //This is where the error is. I always get "realData is not defined".
}
function myGraph(realData)
{
/*
for(var i = 0; i < SAarray.length; i++) // Loop which will load the channel data from the SA objects into the data array for graphing.
{
var data[i] = SAarray[i];
}
*/
console.log("myGraph is called");
var bar = new RGraph.Bar('channelStatus', realData);
bar.Set('labels', ['1','2','3','4','5','6','7','8']);
bar.Set('gutter.left', 50);
bar.Set('gutter.bottom', 40);
bar.Set('ymax',100);
bar.Set('ymin',0);
bar.Set('scale.decimals',1);
bar.Set('title','Channel Status');
bar.Set('title.yaxis','Status (1 is on, 0 is off)');
bar.Set('title.xaxis','Channel Number');
bar.Set('title.xaxis.pos',.1);
bar.Set('background.color','white');
bar.Set('colors', ['Gradient(#a33:red)']);
bar.Set('colors', ['red']);
bar.Set('key',['Occupied','Unoccupied']);
bar.getShapeByX(2).Set('colors',barColor(data[0]));
bar.Draw();
}
Because strings (as code) passed to setInterval execute in the global scope, therefore the realData parameter isn't available. There's rarely a good reason to pass a string to setInterval. Instead, use:
setInterval(function () {
myGraph(realData);
}, 1000);
Reference:
https://developer.mozilla.org/en-US/docs/Web/API/window.setInterval
Try it without it needing to evaluate a string:
setInterval(function() {
myGraph(realData);
},1000);
Any time you are using setTimeout or setInterval, you should opt for passing an actual function instead of a string.