Postmessage in a loop - javascript

I want to bombard the receiver with say 1M messages, but also that the receiver will get each message as soon as possible. The naive way is to loop 1M times and just send a message to the receiver with postmessage. Doesn't work.
What i get is that the whole 1M messages are queued, and only when the code finishes, the receiver starts processing them.
What i need to happen is that the sender will send 1M messages and as he keeps on sending the messages the receiver simultaneously will process them.
For example, what i have now is something like this:
sender : send m1.
sender : send m2.
sender : send m3.
receiver : received m1.
receiver : received m2.
receiver : received m3.
What i want:
sender : send m1.
receiver : received m1.
sender : send m2.
receiver : received m2.
sender : send m3.
receiver : received m3.
How can i achieve this? I can not make the receiver send acks. My goal is to send as many massages as i can the fastest.
Edit: The code i have now:
Sender:
function sendx(x){
console.log("start spam");
for(let i=0; i<200000; i++){
window.opener.postMessage(x, '*');
}
console.log("done");
}
Receiver:
window.addEventListener("message", r_function );
function r_function(event)
{
let index = event.data;
let junk = something(index);
return junk;
}
Where the sender is a new window created by the receiver. What i get in practice is that only when the 'sendx' function ends, the receiver start receiving messages.

What i need to happen is that the sender will send 1M messages and as he keeps on sending the messages the receiver simultaneously will process them.
That's what happens already.
const worker = new Worker(URL.createObjectURL(
new Blob([worker_script.textContent])
));
let logged_first = false;
worker.onmessage = e => {
if(e.data === "spam") {
if(!logged_first) {
console.log('received first message at', new Date().toLocaleString());
logged_first = true; // ignore next messages
}
}
else {
console.log(e.data);
}
}
<script type="text/worker-script" id="worker_script">
const now = performance.now();
postMessage("start spamming at " + new Date().toLocaleString());
while(performance.now() - now < 5000) {
postMessage('spam');
}
postMessage("end spamming at " + new Date().toLocaleString());
</script>
However, for it to work, there is one big condition that needs to be met:
Your two JavaScript instances (sender & receiver) must run on different threads.
That is, if you were doing it using a MessageChannel on the same thread, then it would obviously be unable to treat the messages at the same time it's sending it:
const channel = new MessageChannel();
channel.port1.onmessage = e => {
console.log('received first message at', new Date().toLocaleString());
channel.port1.onmessage = null; // ignore next messages
};
const now = performance.now();
console.log("start spamming at ", new Date().toLocaleString());
while(performance.now() - now < 5000) {
channel.port2.postMessage('spam');
}
console.log("end spamming at ", new Date().toLocaleString());
And if you are dealing with an iframe or an other window, you can not be sure that you'll meet this condition. Browsers all behave differently, here, but they will all run at least some windows on the same process. You have no control as to which process will be used, and hence can't guarantee that you'll run in an other one.
So the best you can do, is to run your loop in a timed-loop, which will let the browser some idle time where it will be able to process other windows event loops correctly.
And the fastest timed-loop we have is actually the one postMessage offers us.
So to do what you wish, the best would be to run each iteration of your loop in the message event of a MessageChannel object.
For this, generator function* introduced in ES6 are quite useful:
/***************************/
/* Make Fake Window part */
/* ONLY for DEMO */
/***************************/
const fake_win = new MessageChannel();
const win = fake_win.port1; // window.open('your_url', '')
const opener = fake_win.port2; // used in Receiver
/********************/
/* Main window part */
/********************/
const messages = [];
win.onmessage = e => {
messages.push(e.data);
};
!function log_msg() {
document.getElementById('log').textContent = messages.length;
requestAnimationFrame(log_msg);
}();
/*******************/
/* Receiver part */
/*******************/
// make our loop a Generator function
function* ourLoopGen(i) {
while(i++ < 1e6) {
opener.postMessage(i);
yield i;
}
}
const ourLoop = ourLoopGen(0);
// here we init our time-loop
const looper = new MessageChannel();
looper.port2.onmessage = e => {
const result = ourLoop.next();
if(!result.done)
looper.port1.postMessage(''); // wait next frame
};
// start our time-loop
looper.port1.postMessage('');
<pre id="log"></pre>
We could also do the same using ES6 async/await syntax, since we can be sure that nothing else in our MessageChannel powered timed-loop will interfere (unlike in a Window's postMessage), we can promisify it:
/***************************/
/* Make Fake Window part */
/* ONLY for DEMO */
/***************************/
const fake_win = new MessageChannel();
const win = fake_win.port1; // window.open('your_url', '')
const opener = fake_win.port2; // used in Receiver
/********************/
/* Main window part */
/********************/
const messages = [];
win.onmessage = e => {
messages.push(e.data);
};
! function log_msg() {
document.getElementById('log').textContent = messages.length;
requestAnimationFrame(log_msg);
}();
/*******************/
/* Receiver part */
/*******************/
const looper = makeLooper();
// our async loop function
async function loop(i) {
while (i++ < 1e6) {
opener.postMessage(i);
await looper.next()
}
}
loop(0);
// here we init our promisified time-loop
function makeLooper() {
const engine = new MessageChannel();
return {
next() {
return new Promise((res) => {
engine.port2.onmessage = e => res();
engine.port1.postMessage('');
});
}
};
};
<pre id="log"></pre>
But it could obviously also be made entirely ES5 style with callbacks and everything:
/***************************/
/* Make Fake Window part */
/* ONLY for DEMO */
/***************************/
var fake_win = new MessageChannel();
var win = fake_win.port1; // window.open('your_url', '')
var opener = fake_win.port2; // used in Receiver
/********************/
/* Main window part */
/********************/
var messages = [];
win.onmessage = function(e) {
messages.push(e.data);
};
!function log_msg() {
document.getElementById('log').textContent = messages.length;
requestAnimationFrame(log_msg);
}();
/*******************/
/* Receiver part */
/*******************/
var i = 0;
var looper = makeLooper(loop);
// our callback loop function
function loop() {
if (i++ < 1e6) {
opener.postMessage(i);
looper.next(loop);
}
}
loop(0);
// here we init our promisified time-loop
function makeLooper(callback) {
var engine = new MessageChannel();
return {
next: function() {
engine.port2.onmessage = function(e) {
callback();
}
engine.port1.postMessage('');
}
};
};
<pre id="log"></pre>
But note that browsers will anyway throttle the pages that are not in focus, so you may have slower results than in these snippets.

Related

How to return memory of terminated web worker safely?

I made to use web workers to upload files.
It handle with small size files.
But with large size file, the speed is getting very slow and my script causes web page collapse.
It does not return memory of web workers.
See the attachment.
The Dedicated Workers keep being accumulated and consume GB memory, when large file is being uploaded.
And I see this warning accumulating whenever web worker call close()
Scripts may close only the windows that were opened by them.
I throttled threadsQuantity as 5.
I think the number of web workers should not be exceeded more than 5.
class Queue {
constructor() {
this.timestamp = new Date().getTime();
this.activeConnections = {};
this.threadsQuantity = 5;
}
async sendNext() {
const activeConnections = Object.keys(this.activeConnections).length;
if (activeConnections >= this.threadsQuantity) {
return;
}
if (!this.chunksQueue.length) {
if (!activeConnections) {
this.complete();
}
return;
}
let chunkId = this.chunksQueue.pop();
this.activeConnections[chunkId] = true;
this.sendChunk( chunkId) ;
}
sendChunk( chunkId) {
if (window.Worker) {
let chunk = this.getChunk( chunkId)
const myWorker = new Worker("/assets/js/worker.js?v=" + this.timestamp);
myWorker.postMessage([this.timestamp, chunkId, chunk]);
myWorker.onmessage = (e) => {
var obj = JSON.parse(e.data)
if( obj.success) {
delete this.activeConnections[chunkId];
this.sendNext()
close();
} else {
sendChunk( chunkId);
}
}
}
}
}
I tried with close() , self.close() but all got same warning and failed.
I tried with this.close(), but it cause this error.
app.0a4dcc55.js:32 Uncaught TypeError: this.close is not a function
at _.onmessage
How can I kill terminated web workers safely during process ?

Accessing Service Worker saved IndexedDB data from Content Script via Chrome Runtime Messaging

In a Chrome Extension, I have no problem adding, updating, and removing data to/from an IndexedDB database accessed by my service worker with Chrome Runtime Messaging sent from my content script. My trouble is doing a full table read from my content script. I do a console.log() to dump out the property before I send it back in my sendResponse in the Chrome Runtime Messaging, and I see the data there properly, but the content script receives an undefined. I assume this is because of the asynchronous nature of getting the data. I tried promises and async/await and the combination thereof and I just can't seem to get anything except an undefined in my content script on the message back from the service worker. I also ran sending a test array back and that worked just fine -- but receiving the IndexedDB table data does not work in the message passing. I also tried to JSONify the data and that didn't help either. What's the catch?
service-worker.js
importScripts('modules/idb.js');
var SW = {};
SW.onReady = function(){
chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
(o.readTable) && sendResponse(SW.readTable(o,sender));
});
};
SW.readTable = function(o,sender){
var sTable = o.table;
new Promise((resolve) => {
IDB.readTable(sTable,function(asEntries){
resolve(asEntries);
});
}).then((asEntries) => {
console.log('SW asEntries',asEntries); // this shows me valid data in tests
var o = {};
// can also change this to fake data with asEntries being a string array and bug goes away in content.js
o.response = asEntries;
return o;
});
};
SW.onReady();
modules/idb.js
var IDB = {};
// Requires storage (or, even better, unlimitedStorage) permission in your manifest.json file.
// Note also that dev console of service worker will not show data -- have to use toolbar button popup panel (if you have one) and
// dev console from there, or code to access it, which sucks.
IDB.connectStore = function(sTable,sReadWriteSetting,fn){
var conn = indexedDB.open('unlimitedStorage', 1);
conn.onupgradeneeded = function(e) {
var db = e.target.result;
db.createObjectStore(sTable);
};
conn.onsuccess = function(e) {
var db = e.target.result;
var tx = db.transaction(sTable,sReadWriteSetting);
var store = tx.objectStore(sTable);
fn(db,tx,store);
};
};
IDB.addToTable = function(sTable,sKey,sVal){
IDB.connectStore(sTable,'readwrite',function(db,tx,store){
if ((sKey === undefined) || (sKey === '') || (sKey === null) || (sKey === false)) { // auto key by increment
var req = store.count();
req.onsuccess = function(e){
sKey = e.target.result + 1;
store.add(sVal,sKey);
tx.complete;
}
} else {
store.add(sVal,sKey);
tx.complete;
}
});
};
IDB.removeFromTable = function(sTable,sKey){
IDB.connectStore(sTable,'readwrite',function(db,tx,store){
store.delete(sKey);
tx.complete;
});
};
IDB.readTableByKey = function(sTable,sKey,fn){
IDB.connectStore(sTable,'readonly',function(db,tx,store){
var req = store.get(sKey);
req.onerror = function(e){
fn(e.target.result);
}
req.onsuccess = function(e){
fn(e.target.result);
}
});
};
IDB.readTable = function(sTable,fn){
IDB.connectStore(sTable,'readonly',function(db,tx,store){
var req = store.getAll();
req.onerror = function(e){
fn(e.target.result);
}
req.onsuccess = function(e){
fn(e.target.result);
}
});
};
content.js
var CONTENT = {};
CONTENT.onReady = function(){
var o = {};
o.readTable = true;
o.table = 'loadTimes';
chrome.runtime.sendMessage(o,function(response){
if (response.response) { // errors here with response property being undefined
console.log('CONTENT RCVD asEntries',response.response);
}
});
};
CONTENT.onReady();
Chrome extensions API, unlike Firefox WebExtensions, can't handle Promise returned from a callback or provided in sendResponse, https://crbug.com/1185241.
There's also a bug in your readTable: you need to add return before new Promise((resolve)
The solution is two-fold:
Use return true from the callback to allow asynchronous sendResponse
Call sendReponse inside .then of a Promise chain.
chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
if (o.readTable) {
SW.readTable(o,sender).then(sendResponse);
return true;
} else {
sendResponse(); // Chrome 99-101 bug workaround, https://crbug.com/1304272
}
});
Do not use this answer. It is here for posterity reasons and is just a workaround. The chosen solution works.
The fix is to return data in a different message thread:
In the service worker in SW.readTable(), just return variable o with o.response = true and then ignore the response in the content script.
Before returning the variable o from SW.readTable(), do a chrome.runtime.sendMessage({readTableResult = true, data: asEntries},function(response){ /* ignore response */});
In the content script, ignore any response back from the readTable message. So, the if (response.response) {...} condition can be eliminated.
In the content script, add a message listener with chrome.runtime.onMessage.addListener(o, sender, sendResponse) and look for the condition (o.readTableResult). Once received, the o.data will now contain the asEntries data.

IBM MQ How read one by one message, not all available in a queue manager at once?

Now, my app receives all available messages in a Queue manager. I collect them locally and process one by one.
Could do I configure it to receive one message, do some work (it can take some time), delete the received message, repeat? Is this behavior possible with IBM MQ?
The code was updated
function listenToMQ() {
const qMgr = inbound.queueManagerName;
const qName = inbound.queueName;
const connName = inbound.host;
const cno = new mq.MQCNO();
const sco = new mq.MQSCO();
const csp = new mq.MQCSP();
const cd = new mq.MQCD();
cno.SecurityParms = csp;
csp.UserId = inbound.userID;
csp.authenticationType = 0;
cno.Options |= MQC.MQCNO_CLIENT_BINDING;
cd.ConnectionName = connName;
cd.ChannelName = inbound.channelName;
cd.SSLClientAuth = MQC.MQSCA_OPTIONAL;
cd.MaxMsgLength = 104857600;
cno.ClientConn = cd;
cno.SSLConfig = sco;
mq.setTuningParameters({
syncMQICompat: true });
mq.Connx(qMgr, cno, function(err, hConn) {
if (err) {
logger.errorLogger().error(err.message);
} else {
const od = new mq.MQOD();
od.ObjectName = qName;
od.ObjectType = MQC.MQOT_Q;
const openOptions = MQC.MQOO_BROWSE;
mq.Open(hConn, od, openOptions, function(err, hObj) {
queueHandle = hObj;
if (err) {
logger.errorLogger().error(err.message);
} else {
getMessages();
}
});
} }); }
function getMessages() {
const md = new mq.MQMD();
const gmo = new mq.MQGMO();
gmo.Options =
MQC.MQGMO_NO_SYNCPOINT |
MQC.MQGMO_MQWI_UNLIMITED |
MQC.MQGMO_CONVERT |
MQC.MQGMO_FAIL_IF_QUIESCING;
gmo.Options |= MQC.MQGMO_BROWSE_FIRST;
gmo.MatchOptions = MQC.MQMO_NONE;
mq.setTuningParameters({
getLoopPollTimeMs: 500 }); mq.Get(queueHandle, md, gmo, getCB); }
function getCB(err, hObj, gmo, md, buf, hConn) {
if (md.Format == "MQSTR") {
console.log(md);
const message = decoder.write(buf);
updateDB(getMetaFeed(message));
}
mq.Cmit(hConn);
}
gmo.Options &= ~MQC.MQGMO_BROWSE_FIRST;
gmo.Options |= MQC.MQGMO_BROWSE_NEXT; }
Yes, most certainly you can.
Your application can get one message, perhaps using syncpoint if it is a message that drives some work that needs done, do the work and then when the work is done commit the get of the message and then go and get the next one. If the work that needs to be done is also transactional (e.g. update a database), then a global transaction could be used to commit both the MQ message and the update of the other transactional resource at the same time.
The code you show in your question appears to be doing a browse of messages (queue opened with MQOO_BROWSE and then messages read using MQGMO_BROWSE_FIRST and then MQGMO_BROWSE_NEXT). I'm not sure how or when your application currently removes the messages from the queue?
Your current code appears to be processing the messages one by one already, so the only changes needed would be to the get options (and to add a commit call).

Socket.io emitting values inside ES6 class

I wonder if any smart individuals could show me how to implement Socket.IO in an OOP environment with ES6 classes. The main problem I keep running into with Socket.io is passing around the server object, in my case called 'io'. Almost every example I've seen of socket.io has been pure spaghetti code, one file with many socket related events and logic. First I tried to pass the server object, io, to new class's constructor, but for some reason you end up with a nasty "RangeError: Maximum call stack size exceeded" error message. Then I've tried to wrap my classes in module.exports function which parameter should contain the io object. Which is fine for the first class. Let's say I pass the io object into my Game, great works as expected. But when I try to reference the io object down to the Round class(Game holds an array of Rounds) I can't. Because that is one hell of a bad practice in NodeJS, require should be global and not inside the modules/functions. So I'm once again back with the same issue.
app.js(where I require the main sockets file)
const io = socketio(server, { origins: '*:*' });
...
require('./sockets')(io);
sockets/index.js(where I initialize my game server, and handle incoming messages from client sockets)
const actions = require('../actions.js');
const chatSockets = require('./chat-sockets');
const climbServer = require('./climb-server');
const authFunctions = require('../auth-functions');
module.exports = (io) => {
io.on('connection', (client) => {
console.log('client connected...');
// Standard join, verify the requested room; if it exists let the client join it.
client.on('join', (data) => {
console.log(data);
console.log(`User ${data.username} tries to join ${data.room}`);
console.log(`Client joined ${data.room}`);
client.join(data.room);
});
client.on('disconnect', () => {
console.log('Client disconnected');
});
client.on(actions.CREATE_GAME, (hostParticipant) => {
console.log('CREATE_GAME', hostParticipant);
// Authorize socket sender by token?
// Create a new game, and set the host to the host participant
climbServer.createGame(io, hostParticipant);
});
client.on(actions.JOIN_GAME, (tokenizedGameId) => {
console.log('JOIN_GAME');
const user = authFunctions.getPayload(tokenizedGameId.token);
// Authorize socket sender by token?
// Create a new game, and set the host to the host participant
const game = climbServer.findGame(tokenizedGameId.content);
game.joinGame(user);
});
});
};
climbServer.js(My game server that keeps track of active games)
const actions = require('../actions.js');
const Game = require('../models/game');
const climbServer = { games: { }, gameCount: 0 };
climbServer.createGame = (io, hostParticipant) => {
// Create a new game instance
const newGame = new Game(hostParticipant);
console.log('New game object created', newGame);
// Store it in the list of game
climbServer.games[newGame.id] = newGame;
// Keep track
climbServer.gameCount += 1;
// Notify clients that a new game was created
io.sockets.in('climb').emit(actions.CLIMB_GAME_CREATED, newGame);
};
climbServer.findGame = gameId => climbServer.games[gameId];
module.exports = climbServer;
Game.js(ES6 class that SHOULD be able to emit to all connected sockets)
const UUID = require('uuid');
const Round = require('./round');
class Game {
// Constructor
constructor(hostParticipant) {
this.id = UUID();
this.playerHost = hostParticipant;
this.playerClient = null;
this.playerCount = 1;
this.rounds = [];
this.timestamp = Date.now();
}
joinGame(clientParticipant) {
console.log('Joining game', clientParticipant);
this.playerClient = clientParticipant;
this.playerCount += 1;
// Start the game by creating the first round
return this.createRound();
}
createRound() {
console.log('Creating new round at Game: ', this.id);
const newRound = new Round(this.id);
return this.rounds.push(newRound);
}
}
module.exports = Game;
Round.js(ES6 class that is used by the Game class(stored in a rounds array))
const actions = require('../actions.js');
class Round {
constructor(gameId) {
console.log('Initializing round of gameId', gameId);
this.timeLeft = 60;
this.gameId = gameId;
this.winner = null;
this.timestamp = Date.now();
// Start countdown when class is instantiated
this.startCountdown();
}
startCountdown() {
const countdown = setInterval(() => {
// broadcast to every client
io.sockets.in(this.gameId).emit(actions.ROUND_TIMER, { gameId: this.gameId, timeLeft: this.timeLeft });
if (this.timeLeft === 0) {
// when no time left, stop counting down
clearInterval(countdown);
this.onRoundEnd();
} else {
// Countdown
this.timeLeft -= 1;
console.log('Countdown', this.timeLeft);
}
}, 1000);
}
onRoundEnd() {
// Evaluate who won
console.log('onRoundEnd: ', this.gameId);
}
}
module.exports = Round;
TO SUMMARIZE with a question: How can I pass a reference of io to my classes so that I'm able to emit to connected sockets within these classes?
This doesn't necessarily have to be ES6 classes, it can be NodeJS objects using the .prototype property. I just want a mainatainable way to handle my game server with sockets... ANY HELP IS APPRECIATED!
After hours upon hours I figured out a solution. If anyone runs into the same thing check my solution out below. Not the best, but much better than putting all socket related code in one file...
Game.js(ES6 Class). Focus on the first line containing 'module.exports'.
const GameFactory = require('../models/game');
const climbServer = { games: { }, gameCount: 0 };
climbServer.createGame = (io, hostParticipant) => {
// Create a new game instance
const Game = GameFactory(io);
const newGame = new Game(hostParticipant);
console.log('New game object created', newGame);
// Store it in the list of game
climbServer.games[newGame.id] = newGame;
// Keep track
climbServer.gameCount += 1;
return newGame;
};
climbServer.findGame = gameId => climbServer.games[gameId];
module.exports = climbServer;
The trick is to use this factory pattern where you first declare:
const GameFactory = require('../models/game');
Then initialize the factory with passing in the Socket.io server object, in my case 'io'. IF YOU pass it in via the constructor you end up with a RangeError, therefore this is the only way. Once again not certain how this code performs in comparison to spaghetti code.
const Game = GameFactory(io);
Finally, you can now instantiate instances of your class:
const newGame = new Game(hostParticipant);
If anyone have improvements or thoughts, please leave me a comment. Still uncertain about the quality of this code.

About HTML5 web worker using in 1 single js file [duplicate]

As far as I can tell, web workers need to be written in a separate JavaScript file, and called like this:
new Worker('longrunning.js')
I'm using the closure compiler to combine and minify all my JavaScript source code, and I'd rather not have to have my workers in separate files for distribution. Is there some way to do this?
new Worker(function() {
//Long-running work here
});
Given that first-class functions are so crucial to JavaScript, why does the standard way to do background work have to load a whole other JavaScript file from the web server?
http://www.html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers
What if you want to create your worker script on the fly, or create a self-contained page without having to create separate worker files? With Blob(), you can "inline" your worker in the same HTML file as your main logic by creating a URL handle to the worker code as a string
Full example of BLOB inline worker:
<!DOCTYPE html>
<script id="worker1" type="javascript/worker">
// This script won't be parsed by JS engines because its type is javascript/worker.
self.onmessage = function(e) {
self.postMessage('msg from worker');
};
// Rest of your worker code goes here.
</script>
<script>
var blob = new Blob([
document.querySelector('#worker1').textContent
], { type: "text/javascript" })
// Note: window.webkitURL.createObjectURL() in Chrome 10+.
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e) {
console.log("Received: " + e.data);
}
worker.postMessage("hello"); // Start the worker.
</script>
The html5rocks solution of embedding the web worker code in HTML is fairly horrible.
And a blob of escaped JavaScript-as-a-string is no better, not least because it complicates work-flow (Closure compiler can't operate on strings).
Personally I really like the toString methods, but #dan-man THAT regex!
My preferred approach:
// Build a worker from an anonymous function body
var blobURL = URL.createObjectURL( new Blob([ '(',
function(){
//Long-running work here
}.toString(),
')()' ], { type: 'application/javascript' } ) ),
worker = new Worker( blobURL );
// Won't be needing this anymore
URL.revokeObjectURL( blobURL );
Support is the intersection of these three tables:
http://caniuse.com/#feat=webworkers
http://caniuse.com/#feat=blobbuilder
http://caniuse.com/#feat=bloburls
This won't work for a SharedWorker however, because the URL must be an exact match, even if the optional 'name' parameter matches. For a SharedWorker, you'll need a separate JavaScript file.
2015 update - The ServiceWorker singularity arrives
Now there's an even more powerful way of solving this problem.
Again, store the worker code as a function, (rather than a static string) and convert using .toString(), then insert the code into CacheStorage under a static URL of your choice.
// Post code from window to ServiceWorker...
navigator.serviceWorker.controller.postMessage(
[ '/my_workers/worker1.js', '(' + workerFunction1.toString() + ')()' ]
);
// Insert via ServiceWorker.onmessage. Or directly once window.caches is exposed
caches.open( 'myCache' ).then( function( cache )
{
cache.put( '/my_workers/worker1.js',
new Response( workerScript, { headers: {'content-type':'application/javascript'}})
);
});
There are two possible fall-backs. ObjectURL as above, or more seamlessly, put a real JavaScript file at /my_workers/worker1.js
Advantages of this approach are:
SharedWorkers can also be supported.
Tabs can share a single cached copy at a fixed address. The blob approach proliferates random objectURLs for every tab.
You can create a single JavaScript file that is aware of its execution context and can act as both a parent script and a worker. Let's start off with a basic structure for a file like this:
(function(global) {
var is_worker = !this.document;
var script_path = is_worker ? null : (function() {
// append random number and time to ID
var id = (Math.random()+''+(+new Date)).substring(2);
document.write('<script id="wts' + id + '"></script>');
return document.getElementById('wts' + id).
previousSibling.src;
})();
function msg_parent(e) {
// event handler for parent -> worker messages
}
function msg_worker(e) {
// event handler for worker -> parent messages
}
function new_worker() {
var w = new Worker(script_path);
w.addEventListener('message', msg_worker, false);
return w;
}
if (is_worker)
global.addEventListener('message', msg_parent, false);
// put the rest of your library here
// to spawn a worker, use new_worker()
})(this);
As you can see, the script contains all code for both the parent's and the worker's point of view, checking if its own individual instance is a worker with !document. The somewhat unwieldy script_path computation is used to accurately calculate the script's path relative to the parent page, as the path supplied to new Worker is relative to the parent page, not the script.
Using the Blob method, how about this for a worker factory:
var BuildWorker = function(foo){
var str = foo.toString()
.match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1];
return new Worker(window.URL.createObjectURL(
new Blob([str],{type:'text/javascript'})));
}
So you could use it like this...
var myWorker = BuildWorker(function(){
//first line of worker
self.onmessage(){....};
//last line of worker
});
EDIT:
I've just extended this idea further to make it easier to do cross-thread communication: bridged-worker.js.
EDIT 2:
The above link is to a gist I created. Someone else later turned it into an actual repo.
Web workers operate in entirely separate contexts as individual Program's.
This means that code cannot be moved from one context to another in object form, as they would then be able to reference objects via closures belonging to the other context.
This is especially crucial as ECMAScript is designed to be a single threaded language, and since web workers operate in separate threads, you would then have the risk of non-thread-safe operations being performed.
This again means that web workers need to be initialized with code in source form.
The spec from WHATWG says
If the origin of the resulting
absolute URL is not the same as the
origin of the entry script, then throw
a SECURITY_ERR exception.
Thus, scripts must be external files
with the same scheme as the original
page: you can't load a script from a
data: URL or javascript: URL, and an
https: page couldn't start workers
using scripts with http: URLs.
but unfortunately it doesn't really explain why one couldn't have allowed passing a string with source code to the constructor.
Recent answer (2018)
You can use Greenlet:
Move an async function into its own thread. A simplified single-function version of Workerize.
Example:
import greenlet from 'greenlet'
const getName = greenlet(async username => {
const url = `https://api.github.com/users/${username}`
const res = await fetch(url)
const profile = await res.json()
return profile.name
})
console.log(await getName('developit'))
a better to read way for a inline worker..
var worker_fn = function(e)
{
self.postMessage('msg from worker');
};
var blob = new Blob(["onmessage ="+worker_fn.toString()], { type: "text/javascript" });
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e)
{
alert(e.data);
};
worker.postMessage("start");
Taking Adria's response and putting it in a copy-pastable function which works with current Chrome and FF but not IE10 (worker from blob causes a security error).
var newWorker = function (funcObj) {
// Build a worker from an anonymous function body
var blobURL = URL.createObjectURL(new Blob(
['(', funcObj.toString(), ')()'],
{type: 'application/javascript'}
));
var worker = new Worker(blobURL);
// Won't be needing this anymore
URL.revokeObjectURL(blobURL);
return worker;
}
And here's a working example http://jsfiddle.net/ubershmekel/YYzvr/
A simple promisified version, Function#callAsWorker, that takes a thisArg and arguments (just like call), and returns a promise:
Function.prototype.callAsWorker = function (...args) {
return new Promise( (resolve, reject) => {
const code = `self.onmessage = e => self.postMessage((${this.toString()}).call(...e.data));`,
blob = new Blob([code], { type: "text/javascript" }),
worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = e => (resolve(e.data), worker.terminate());
worker.onerror = e => (reject(e.message), worker.terminate());
worker.postMessage(args);
});
}
// Demo
function add(...nums) {
return nums.reduce( (a,b) => a+b );
}
// Let the worker execute the above function, with the specified arguments
add.callAsWorker(null, 1, 2, 3).then(function (result) {
console.log('result: ', result);
});
So I think we have another cool option for this now, thanks to template literals in ES6. That allows us to dispense with the extra worker function (and its weird scope) and just write the code that's intended for the worker as multiline text, much like the case where we were using to store text, but without actually needing a document or DOM to do that in. Example:
const workerScript = `
self.addEventListener('message', function(e) {
var data = e.data;
console.log('worker recieved: ',data);
self.postMessage('worker added! :'+ addOne(data.value));
self.close();//kills the worker
}, false);
`;
Here's a gist of the rest of that approach.
Note that we can pull in any extra function dependencies we want into the worker just by collecting them into an array and running .toString on each of them to reduce them down into strings as well (should work as long as they are function declarations) and then just prepending that to the script string. That way we don't have to importScripts that we might already have bundled into the scope of the code we're writing.
The only real downside to this particular version is that linters won't be able to lint the service worker code (since it's just a string), which is an advantage for the "separate worker function approach."
Depending on your use case you can use something like
task.js Simplified interface for getting CPU intensive code to run on all cores (node.js, and web)
A example would be
function blocking (exampleArgument) {
// block thread
}
// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);
// run task on a autoscaling worker pool
blockingAsync('exampleArgumentValue').then(result => {
// do something with result
});
Take a look at the vkThread plugin. With htis plugin you can take any function in your main code and execute it in a thread (web worker). So, you don't need to create a special "web-worker file".
http://www.eslinstructor.net/vkthread/
--Vadim
I think the better way to do this is using a Blob object, below you can see a simple example.
// create a Blob object with a worker code
var blob = new Blob(["onmessage = function(e) { postMessage('msg from worker'); }"]);
// Obtain a blob URL reference to our worker 'file'.
var blobURL = window.URL.createObjectURL(blob);
// create a Worker
var worker = new Worker(blobURL);
worker.onmessage = function(e) {
console.log(e.data);
};
worker.postMessage("Send some Data");
Try to use jThread. https://github.com/cheprasov/jThread
// You can use simple calling like this
jThread(
function(arr){
//... some code for Worker
return arr;
}
,function(arr){
//... done code
}
)( [1,2,3,4,5,6,7] ); // some params
here console:
var worker=new Worker(window.URL.createObjectURL(new Blob([function(){
//Long-running work here
postMessage('done');
}.toString().split('\n').slice(1,-1).join('\n')],{type:'text/javascript'})));
worker.addEventListener('message',function(event){
console.log(event.data);
});
https://developer.mozilla.org/es/docs/Web/Guide/Performance/Using_web_workers
// 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
});
};
})();
Use my tiny plugin https://github.com/zevero/worker-create
var worker_url = Worker.createURL(function(e){
self.postMessage('Example post from Worker'); //your code here
});
var worker = new Worker(worker_url);
This is just an addition to above - I have a nice templates for testing web workers in jsFiddle. Rather than Blob it uses jsFiddles ?js api:
function workerFN() {
self.onmessage = function(e) {
switch(e.data.name) {
case "" :
break;
default:
console.error("Unknown message:", e.data.name);
}
}
}
// This is a trick to generate real worker script that is loaded from server
var url = "/echo/js/?js="+encodeURIComponent("("+workerFN.toString()+")()");
var worker = new Worker(url);
worker.addEventListener("message", function(e) {
switch(e.data.name) {
case "" :
break;
default:
console.error("Unknown message:", e.data.name);
}
})
Normal web worker and shared worker templates are available.
I discovered that CodePen currently does not syntax-highlight inline <script> tags that are not type="text/javascript" (or which have no type attribute).
So I devised a similar but slightly different solution using labeled blocks with break, which is the only way you can bail from a <script> tag without creating a wrapper function (which is unnecessary).
<!DOCTYPE html>
<script id="worker1">
worker: { // Labeled block wrapper
if (typeof window === 'object') break worker; // Bail if we're not a Worker
self.onmessage = function(e) {
self.postMessage('msg from worker');
};
// Rest of your worker code goes here.
}
</script>
<script>
var blob = new Blob([
document.querySelector('#worker1').textContent
], { type: "text/javascript" })
// Note: window.webkitURL.createObjectURL() in Chrome 10+.
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e) {
console.log("Received: " + e.data);
}
worker.postMessage("hello"); // Start the worker.
</script>
One-liner for running functions in workers:
const FunctionalWorker = fn => new Worker(window.URL.createObjectURL(new Blob(["(" + fn.toString() + ")()"], {type: "text/javascript"})));
Example usage:
let fn = FunctionalWorker(() => {
self.postMessage("hi");
});
fn.onmessage = msg => {
console.log(msg);
};
my take on it:
function BuildWorker(fn){
var str = fn.toString().match(/^[^{]+{([\s\S]+)}\s*$/m)[1];
return new Worker(window.URL.createObjectURL(
new Blob([str],{type:'text/javascript'})));
}
function createAsyncWorker(fn){
// asyncworker=createAsyncWorker(function(){
// importScripts('my_otherscript.js');
// self.onmessage = function([arg1,arg2]) {
// self.postMessage('msg from worker');
// };
// })
// await asyncworker.postMessage('arg1','value')
// await asyncworker.postMessage('arg1','value')
// asyncworker.worker.terminate()
var worker = BuildWorker(fn);
function postMessage(...message){
let external={}, promise= new Promise((resolve,reject)=>{external.resolve=resolve;external.reject=reject;})
worker.onmessage = function(message){ external.resolve(message.data)};
worker.postMessage(message); // Start the worker.
return promise;
}
return {worker,postMessage};
}
usage example:
autoarima = createAsyncWorker(function(){
importScripts("https://127.0.0.1:11000/arima.js")
self.onmessage=(message)=>{
let [action,arg1,arg2]=message.data
if(action=='load')
{
ARIMAPromise.then(ARIMA1 => {
ARIMA=ARIMA1
autoarima = new ARIMA({ auto: true });
// const ts = Array(10).fill(0).map((_, i) => i + Math.random() / 5)
// const arima = new ARIMA({ p: 2, d: 1, q: 2, P: 0, D: 0, Q: 0, S: 0, verbose: false }).train(ts)
// const [pred, errors] = arima.predict(10)
postMessage('ok')
});
}
if(action=='fit')
{
autoarima.fit(arg1)
postMessage('ok')
}
if(action=='predict')
{
postMessage(autoarima.predict(arg1,arg2))
}
};
})
autoarima.terminate=function(){ this.worker.terminate(); }
autoarima.load=async function(...args){return await this.postMessage('load',...args)}
autoarima.fit=async function(...args){return await this.postMessage('fit',...args)}
autoarima.predict=async function(...args){return await this.postMessage('predict',...args)}
await autoarima.load()
await autoarima.fit(b_values)
await autoarima.predict(1)
You can use web workers in same javascript file using inline webworkers.
The below article will address you to easily understand the webworkers and their limitations and debugging of webworkers.
Mastering in webworkers
I use code like this, you can define your onmessage as a function other than plain text, so the editor can highlight your code and jshint works.
const worker = createWorker();
createWorker() {
const scriptContent = getWorkerScript();
const blob = new Blob([
scriptContent,
], {
type: "text/javascipt"
});
const worker = new Worker(window.URL.createObjectURL(blob));
return worker;
}
getWorkerScript() {
const script = {
onmessage: function (e) {
console.log(e);
let result = "Hello " + e.data
postMessage(result);
}
};
let content = "";
for (let prop in script){
content += `${prop}=${script[prop].toString()}`;
}
return content;
}
Yes, it is possible, I did it using Blob files and passing a callback
I'll show you what a class I wrote does and how it manages the execution of callbacks in the background.
First you instantiate the GenericWebWorker with whatever data you'd like to pass to callback that'll be executing in the Web Worker, that includes functions you want to use, in this case a number, a date and a function called blocker
var worker = new GenericWebWorker(100, new Date(), blocker)
This blocker function will execute an infinite while for n miliseconds
function blocker (ms) {
var now = new Date().getTime();
while(true) {
if (new Date().getTime() > now +ms)
return;
}
}
and then you use it like this
worker.exec((num, date, fnBlocker) => {
/*Everithing here does not block the main thread
and this callback has access to the number, date and the blocker */
fnBlocker(10000) //All of this run in backgrownd
return num*10
}).then(d => console.log(d)) //Print 1000
Now, time to see the magic in the example below
/*https://github.com/fercarvo/GenericWebWorker*/
class GenericWebWorker {
constructor(...ags) {
this.args = ags.map(a => (typeof a == 'function') ? {type:'fn', fn:a.toString()} : a)
}
async exec(cb) {
var wk_string = this.worker.toString();
wk_string = wk_string.substring(wk_string.indexOf('{') + 1, wk_string.lastIndexOf('}'));
var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) );
var wk = new Worker(wk_link);
wk.postMessage({ callback: cb.toString(), args: this.args });
var resultado = await new Promise((next, error) => {
wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data);
wk.onerror = e => error(e.message);
})
wk.terminate(); window.URL.revokeObjectURL(wk_link);
return resultado
}
async parallel(arr, cb) {
var res = [...arr].map(it => new GenericWebWorker(it, ...this.args).exec(cb))
var all = await Promise.all(res)
return all
}
worker() {
onmessage = async function (e) {
try {
var cb = new Function(`return ${e.data.callback}`)();
var args = e.data.args.map(p => (p.type == 'fn') ? new Function(`return ${p.fn}`)() : p);
try {
var result = await cb.apply(this, args); //If it is a promise or async function
return postMessage(result)
} catch (e) { throw new Error(`CallbackError: ${e}`) }
} catch (e) { postMessage({error: e.message}) }
}
}
}
function blocker (ms) {
var now = new Date().getTime();
while(true) {
if (new Date().getTime() > now +ms)
return;
}
}
setInterval(()=> console.log("Not blocked " + Math.random()), 1000)
console.log("\n\nstarting blocking code in Worker\n\n")
var worker = new GenericWebWorker(100, new Date(), blocker)
worker.exec((num, date, fnBlocker) => {
fnBlocker(7000) //All of this run in backgrownd
return num*10
})
.then(d => console.log(`\n\nEnd of blocking code: result ${d}\n\n`)) //Print 1000
You can place the contents of your worker.js file inside backticks (which allows a multiline string constant) and create the worker from a blob like this:
var workerScript = `
self.onmessage = function(e) {
self.postMessage('message from worker');
};
// rest of worker code goes here
`;
var worker =
new Worker(createObjectURL(new Blob([workerScript], { type: "text/javascript" })));
This is handy if for whatever reason you don't want to have separate script tags for the worker.
Another solution is just to wrap the Worker in a function, then creating a blob invoking the function like so:
function workerCode() {
self.onmessage = function (e) {
console.log("Got message from parent", e.data);
};
setTimeout(() => {
self.postMessage("Message From Worker");
}, 2000);
}
let blob = new Blob([
"(" + workerCode.toString() + ")()"
], {type: "text/javascript"});
// Note: window.webkitURL.createObjectURL() in Chrome 10+.
let worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function (e) {
console.log("Received: " + e.data);
};
worker.postMessage("hello"); // Start the worker.
there have been a few answers, but here is another inline version.
note: "self" argument is purely cosmetic for linting purposes, actual worker code starts after first brace, self is as normal
inlineWorker (
"hello world",// initial message to send to worker
function(self){
// inline worker code.
self.onmessage = function (e) {
self.postMessage("thinking...");
for (var i=0;i<100000000;i++) {
var r = Math.random();
}
self.postMessage(e.data.toUpperCase());
}
},function(e){
// optional message handler
document.getElementById("log").innerHTML= "from worker:"+e.data;
});
function inlineWorker (msg,fn,onMsg) {
var
w=window,
U=!!w.webkitURL?w.webkitURL:w.URL,
src=fn.toString(),
s=src.indexOf('{'),
e=src.lastIndexOf('}'),
worker = new Worker(U.createObjectURL(
new Blob([ src.substring(s+1,e-1) ], { type: "text/javascript" })
));
if (typeof onMsg==="function") {
worker.addEventListener("message",onMsg);
}
if (msg) {
worker.postMessage(msg);
}
return worker;
}
<div id="log"></div>
For a Node.js implementation, the following adaptation of Trincot's answer can be employed. Note again that Function.prototype.callAsWorker() takes a thisArg and arguments, just like Function.prototype.call(), and returns a promise.
const { Worker } = require ( 'worker_threads' );
Function.prototype.callAsWorker = function ( ...args ) {
return new Promise( ( resolve, reject ) => {
const code = `
const { parentPort, workerData } = require ( 'worker_threads' );
parentPort.postMessage( ( ${this.toString()} ).call( ...workerData ) )
`;
const worker = new Worker( code, { eval: true, workerData: args } );
worker.on('message', ( msg ) => { resolve( msg ), worker.terminate() } );
worker.on('error', ( err ) => { reject( err ), worker.terminate() } );
worker.on('exit', ( code ) => {
if ( code !== 0 ) {
reject( new Error( `Worker stopped with exit code ${code}.` ) );
}
});
});
}
// Demo
function add( ...nums ) {
return nums.reduce( ( a, b ) => a + b );
}
// Let the worker execute the above function, with the specified arguments
let result = await add.callAsWorker( null, 1, 2, 3 );
console.log( 'result: ', result );
I liked the answer that ifbamoq gave but was not able to comment because of stack overflow's points policy. Therefor I'll give an example that shows some intensive work being done - and how it does not lock the main thread.
All without running into CORS problems with the null origin - if you're like me and like double-clicking the html files and treating them like little programs. :-)
<!DOCTYPE html>
<html>
<head>
<title>Worker example: One-core computation</title>
</head>
<body>
<p>The highest prime number discovered so far is: <div id="result"></div></p>
</body>
<script>
// let worker = new Worker('WebWorker.js'); // lets skip this to avoid null origin issues
let WorkerFn = (event) =>
{
let isPrime = false;
for (let n = 2; n <= 1_000_000; n++)
{
isPrime = true;
for(let i = 2; i <= Math.sqrt(n); i++)
if (n % i == 0)
isPrime = false; // If you can get thru all this shit and survive, ur prime!
if (isPrime)
postMessage(n);
}
}
let worker = new Worker(window.URL.createObjectURL(new Blob(["(" + WorkerFn.toString() + ")()"], {type: "text/javascript"})));
worker.onmessage = (event) =>
{
result.innerHTML = event.data;
}
</script>
</html>
#Trincot's seems to be the best so far. Yet perhaps we can develop it a little further. So the idea is,
Let's not modify the Function.prototype.
Obtain a promisified / threadable version of a function for threaded operation.
Make sure that the function can still be invoked synchronously if need be.
So we define a Threadable class with a spawn method. Once we make our function a member of this class then it is threadable :)
class Threadable extends Function {
constructor(f){
super("...as",`return ${f.toString()}.apply(this,as)`);
}
spawn(...as){
var code = `self.onmessage = m => self.postMessage((${this.toString()}).apply(self,m.data));`,
blob = new Blob([code], {type: "text/javascript"}),
wrkr = new Worker(window.URL.createObjectURL(blob));
return new Promise( (v,x) => ( wrkr.onmessage = m => (v(m.data), wrkr.terminate())
, wrkr.onerror = e => (x(e.message), wrkr.terminate())
, wrkr.postMessage(as)
)
);
}
}
function add(...nums) {
return nums.reduce((a,b) => a+b);
}
var addT = new Threadable(add);
addT.spawn(1,2,3,4)
.then(m => console.log(`Promisified thread returned ${m}`));
console.log(`Synchronous invocation of addT returned ${addT(1,2,3,4)}`);

Categories

Resources