Related
I'm writing an ASP.Net MVC project, and fighting with the frontend a bit. Either my worker isn't sending the message, or the rest of my code isn't receiving the message.
GhostHandler.js
/*
* NEEDED DATA
* ---------------
* TYPE ID TO IMG URL
*/
{
let typeIdToImgUrlWorker = new Worker("/js/Editor/Workers/GetImgURLFromTypeID.js");
// Gets the img url for the type of element
typeIdToImgUrlWorker.onmessage = (event) => {
let imgUrl = event.data[componentTypeID];
console.log(imgUrl);
let ghostCounter = 0;
document.addEventListener('mousemove', (event) => {
console.log("pls");
drawComponentGhost(event.clientX, event.clientY, imgUrl, ghostCounter);
removeComponentGhost(ghostCounter);
ghostCounter += 1;
console.log("drawn and removed a component");
})
}
}
Assume every variable is declared and defined properly
GetImgUrlFromTypeID.js
let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://localhost:44338/Editor/GetImgUrlFromTypeID/', true);
xhr.onload = () => {
if (this.status === 200) {
//let json = JSON.parse(this.responseJson);
postMessage(this.responseJson);
}
}
xhr.send();
I know the worker is being spawned, from the network tab. But the rest of the code in GhostHandler.js isn't being ran. I presume because the worker isn't sending the message properly, therefore the arrow function onmessage isn't being ran.
I want to intercept all XHR requests being sent, and change their URL and headers before the request gets sent.
Found this similar question but there are no answers there.
I tried hooking XMLHttpRequest.prototype.open, But it only gives me access to the response:
(function () {
var origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
console.log(arguments); // prints method ("GET"), URL
console.log(this); // prints response, responseText, responseURL, status, statusText, and onXXX handlers
origOpen.apply(this, arguments);
};
})();
Also tried hooking XMLHttpRequest.prototype.setRequestHeader, but it only gives me access to each header value being set, one by one, and I can't associate it to the URL of the request:
(function () {
var origSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
console.log("header", header);
console.log("value", value);
origSetRequestHeader.apply(this, arguments);
};
})();
I managed to hook XMLHttpRequest.prototype.send to set a custom header, but since I want to change an existing header key, it appends my new value instead of replacing the existing one. Other people encountered the same problem: 1, 2:
(function () {
var origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
arguments[1] = myNewUrl; // arguments[1] holds the URL
this.setRequestHeader('existingHeaderKey', newHeaderValue)
origSend.apply(this, arguments);
};
})();
How can I accomplish this?
The XMLHttpRequest(xhr) interface exposes a very few things. So there is limitation on what you can intercept.
However, we can wrap the xhr objects in Proxy and collect data until send is called. And before sending the request we modify the data at one spot.
const OriginalXHR = XMLHttpRequest;
// wrap the XMLHttpRequest
XMLHttpRequest = function() {
return new Proxy(new OriginalXHR(), {
open(method, url, async, username = null, password = null) {
lg('open');
// collect URL and HTTP method
this.modMethod = method;
this.modUrl = url;
this.open(...arguments);
},
setRequestHeader(name, value) {
lg('set header');
if (!this.modReqHeaders) {
this.modReqHeaders = {};
}
// collect headers
this.modReqHeaders[name] = value;
// do NOT set headers here. Hold back!
// this.setRequestHeader(name, value);
},
send(body = null) {
lg('processing request...');
// do the final processing
// ...
// don't forget to set headers
for (const [name, value] of Object.entries(this.modReqHeaders)) {
this.setRequestHeader(name, value);
}
lg('sending request =>' +
'\n\t\tmethod: \t' + this.modMethod +
'\n\t\turl:\t\t' + this.modUrl +
'\n\t\theaders:\t' + JSON.stringify(this.modReqHeaders));
this.send(body);
},
get(xhr, key) {
if (!key in xhr) return undefined;
let value = xhr[key];
if (typeof value === "function") {
// if wrapped, use the function in proxy
value = this[key] || value;
return (...args) => value.apply(xhr, args);
} else {
//return properties
return value;
}
},
set(xhr, key, value) {
if (key in xhr) {
xhr[key] = value;
}
return value;
}
});
}
console.warn('XMLHttpRequest has been patched!\n XMLHttpRequest: ', XMLHttpRequest);
let url = 'https://baconipsum.com/api/?type=all-meat&sentences=1&start-with-lorem=1';
function getData() {
console.log('fetching lorem ipsum');
let xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerText = this.response[0];
}
};
xhr.open("GET", url, true);
xhr.setRequestHeader('Referer', 'www.google.com');
xhr.setRequestHeader('Accept-Encoding', 'x-compress; x-zip')
xhr.setRequestHeader('Accept-Language', 'de-US,en;q=0.5');
xhr.send();
}
//fancy logging, looks good in dark mode
function lg(msg) {
console.log('%c\t Proxy: ' + msg, 'background: #222; color: #bada55');
}
#demo {
min-height: 100px;
background-color: wheat;
}
<button onclick="getData()">Get data</button>
<div id="demo"></div>
<p>Note: look in the Developer Console for debug logs</p>
You can wrap remaining xhr methods or attributes in the proxy handler as per your requirement.
This may not be as good as service workers. But service workers have following drawbacks:
A service worker is run in a worker context: it therefore has no DOM access, and runs on a different thread to the main JavaScript that powers your app, so it is non-blocking. It is designed to be fully async; as a consequence, APIs such as synchronous XHR and Web Storage can't be used inside a service worker.
Service workers only run over HTTPS, for security reasons. Having modified network requests, wide open to man in the middle attacks would be really bad. In Firefox, Service Worker APIs are also hidden and cannot be used when the user is in private browsing mode.ref
I have been trying to override the methods in order to intercept the xhr requests but seems like everything I do it only prinds '1' for this.readyState.
Does anyone have any idea why?
addInterceptorsToXHRRequests() {
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
const originalStateChangeHandler = this.onreadystatechange;
this.onreadystatechange = function() {
console.log(' ---> ',this.readyState);
if (originalStateChangeHandler instanceof Function) {
originalStateChangeHandler.apply(this, arguments);
console.log('Ready state: ' + this.readyState);
}
};
return originalOpen.apply(this, arguments);
};
}
I am calling this function from index.js, at the end of componentDidMount life cycle method. (pretty big project)
This happens because you bind a function on XMLHttpRequest.prototype.open .
open returns only state 1.
So, you need to attach the onreadystatechange function to the full xhr.
Example
// you probably want the original XMLHttpRequest here...
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
console.log(xhr.readyState);
};
This question was posted a couple of days ago, but since I'm a nub it was filled with spaghetti code and that sort of thing (please pardon the form handling as well) That aside, I've added some notes and given some context, but the problem still lies in the second AJAX call.
This is the error that Chrome is throwing "Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource."
I have hidden the URL because it contains an API key that I would rather not share.
Any and all criticisms are warmly welcomed
/*
This module will take a user's name, return an ID
then search more stats in the api with the ID.
*/
var search = document.getElementById('search');
search.addEventListener('click', function(){
var demo = document.getElementById('demo');
var player_name = document.getElementById('player_name').value;
var player_id;
// Interpolated API URLs
var name_url = 'URL.pre'+player_name+'URL.end';
var stats_url; //nested in the second ajax call to pass updated player_id
// Get player ID
var xhr = new XMLHttpRequest();
var id_return_text;
xhr.onload = function(){
if(xhr.status === 200) {
id_return_text = JSON.parse(xhr.responseText);
player_id = id_return_text[player_name].id;
demo.innerHTML = id_return_text[player_name].name +', your player ID is: '+player_id;
}
};
xhr.open('GET', name_url, true);
xhr.send();
// Search stats with ID
var xhr_2 = new XMLHttpRequest();
var stats_return_text;
xhr.done = function(){
stats_url = "URL.pre"+player_id+"URL.end";
if(xhr_2.status == 200) {
stats_return_text = JSON.parse(xhr_2.responseText);
demo.innerHTML += stats_return_text['playerStatsSummaries'].playerStatType;
}
};
xhr_2.open("GET",stats_url, true);
xhr_2.send();
});
<div id="container">
<img id="duck" src="duck.png" alt="duck">
<div class="form_wrapper">
<h1 id="app_header">*QUACK* What's Your player ID?</h1>
<form>
<input
type="text"
id="player_name"
placeholder="Summoner Name">
<input type="button" id="search" value="Search">
</form>
</div>
<p id="demo"></p>
</div>
<script type="text/javascript" src="script.js"></script>
So your primary error was that if you need to make CORS requests (or any AJAX requests, really), you need to run the code from a server (even localhost).
Google (and most browsers) will freak out at you if your page's protocol is "file:///" and you're trying to load things from the internet (or vice versa). And "file:///" cannot make requests for other files, either.
Future reference: you also can't make "http" requests from an "https" page.
That out of the way, the second issue (the one that was being hidden by CORS security), is that your AJAX requests are being run in parallel right now.
In order to make this work the way you think it should (after the first one returns, run the second one), you would need to:
move all of the code at the bottom, relating to xhr_2 inside of the xhr.onload
move all of the code inside of xhr.done at the bottom inside of the xhr.onload and replace all of the duplicate information (and use the references to the returned results directly)
This results in something like:
var search = document.getElementById('search');
search.addEventListener('click', function(){
var demo = document.getElementById('demo');
var player_name = document.getElementById('player_name').value;
var player_id;
// Interpolated API URLs
var name_url = 'https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/'+player_name+'?api_key=<THIS IS THE API KEY>';
var stats_url; //nested in the second ajax call to pass updated player_id
// Get player ID
var xhr = new XMLHttpRequest();
var id_return_text;
xhr.onload = function(){
if(xhr.status === 200) {
id_return_text = JSON.parse(xhr.responseText);
player_id = id_return_text[player_name].id;
demo.innerHTML = id_return_text[player_name].name +', your player ID is: '+player_id;
// Dropped the XHR_2 stuff here
var xhr_2 = new XMLHttpRequest();
var stats_return_text;
stats_url = "https://na.api.pvp.net/api/lol/na/v1.3/stats/by-summoner/"+player_id+"/summary?season=SEASON2016&api_key=<THIS IS THE API KEY>";
// CHANGED THIS TO BE XHR_2.onload -- IN HERE I KNOW XHR_1 IS ALREADY FINISHED
xhr_2.onload = function(){
if(xhr_2.status == 200) {
stats_return_text = JSON.parse(xhr_2.responseText);
demo.innerHTML += stats_return_text['playerStatsSummaries'].playerStatType;
}
};
xhr_2.open("GET",stats_url, true);
xhr_2.send();
}
};
xhr.open('GET', name_url, true);
xhr.send();
});
That should solve practically all of your woes.
The point of this is that onload is a callback which gets fired long after the program has been run, but xhr_2 was firing immediately after you requested data for xhr_1 (not after it was returning the data).
As such, player_id was undefined.
We want to wait until after we know we have player_id, and we know we have it (or some error) when we're inside the callback to xhr_1.onload.
This gets terribly confusing and very nested, and while I think that Promises and Async Functions / Generators are brilliant solutions for managing that complexity, that's way beyond the scope of this; so instead, I'd suggest looking at some functional composition, to simplify all of this:
function noop () { } // do nothing
function getJSON (url, onload, onerror) {
var xhr = new XMLHttpRequest();
onload = onload || noop; // what I've been given or nothing
onerror = onerror || noop; // " "
xhr.onload = function () {
var data;
var error;
try {
// it's possible for parse to throw on malformed JSON
data = JSON.parse(xhr.responseText);
} catch (e) {
error = e;
}
return error ? onerror(error) : onload(data); // fire one or the other (don't fall into the handler, if onload throws)
};
xhr.onerror = onerror;
xhr.open("GET", url);
xhr.send();
}
// localize URL construction
function buildPlayerIdUrl (name) { return "https://______" + name + "_____"; }
function buildPlayerStatsUrl (id) { return "https://______" + id + "_____"; }
// gets player by name and runs a function after the player has been loaded
function getPlayer (player_name, done, error) {
var id_url = buildPlayerIdUrl(player_name);
function buildPlayer (response) {
var player = response[player_name];
return player;
}
function onload (response) {
done(buildPlayer(response));
}
// Load the JSON, build the player, pass the player to done()
getJSON(url, onload, error);
}
// get stats by player id and runs a function after the stats have been loaded
function getPlayerStats (player_id, done, error) {
var stats_url = buildPlayerStatsUrl(player_id);
function buildStats (response) {
var summary = response.playerStatsSummaries;
return summary;
}
function onload (response) {
done(buildStats(response));
}
// Load the JSON, build the stats, pass the stats to done()
getJSON(stats_url, onload, error);
}
// perform a search by player name
// note: All changes in step-number (1, 2, 3) are asynchronous,
// and thus, must be nested in callbacks of some sort
function search (player_name) {
// Step 1: load the player
getPlayer(playerName, function (player) {
// Step 2a: update the DOM with the player name/id
updatePlayerDom(player);
// Step 2b: load the player stats
getPlayerStats(player.id, function (stats) {
// Step 3: update the DOM with the stats
updateStatsDom(stats);
});
});
}
// player DOM update; keeping it nice and simple
function updatePlayerDom (player) {
document.querySelector(".Player-id").textContent = player.id;
document.querySelector(".Player-name").textContent = player.name;
}
// stats DOM update; same as above
function updateStatsDom (stats) {
document.querySelector(".Player-stats").textContent = stats.playerStatType;
}
// bootstrap yourself to your UI
some_button.onclick = function () {
var player_name = some_input.value;
search(player_name); // kick the whole thing off
};
It's definitely more code, but it's also simpler to make edits to each individual piece, without stepping on the toes of other pieces.
It's (hopefully) also easier to see the _eventual timeline_ of all of the pieces, and how they flow, inside of the search( ) itself.
How can I use create a Web worker from a string (which is supplied via a POST request)?
One way I can think of, but I'm not sure how to implement it, is by creating a data-URI from the server response, and passing that to the Worker constructor, but I've heard that some browsers don't allow this, because of the same origin policy.
MDN states the uncertainty about the origin policy around data URI's:
Note: The URI passed as parameter of the Worker constructor must obey the same-origin policy. There is currently disagreement among browsers vendors on whether data URIs are of the same-origin or not; Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0) and later do allow data URIs as a valid script for workers. Other browsers may disagree.
Here's also a post discussing it on the whatwg.
Summary
blob: for Chrome 8+, Firefox 6+, Safari 6.0+, Opera 15+
data:application/javascript for Opera 10.60 - 12
eval otherwise (IE 10+)
URL.createObjectURL(<Blob blob>) can be used to create a Web worker from a string. The blob can be created using the BlobBuilder API deprecated or the Blob constructor.
Demo: http://jsfiddle.net/uqcFM/49/
// URL.createObjectURL
window.URL = window.URL || window.webkitURL;
// "Server response", used in all examples
var response = "self.onmessage=function(e){postMessage('Worker: '+e.data);}";
var blob;
try {
blob = new Blob([response], {type: 'application/javascript'});
} catch (e) { // Backwards-compatibility
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
blob = new BlobBuilder();
blob.append(response);
blob = blob.getBlob();
}
var worker = new Worker(URL.createObjectURL(blob));
// Test, used in all examples:
worker.onmessage = function(e) {
alert('Response: ' + e.data);
};
worker.postMessage('Test');
Compatibility
Web workers are supported in the following browsers source:
Chrome 3
Firefox 3.5
IE 10
Opera 10.60
Safari 4
This method's support is based on the support of the Blob API and the URL.createObjectUrl method. Blob compatibility:
Chrome 8+ (WebKitBlobBuilder), 20+ (Blob constructor)
Firefox 6+ (MozBlobBuilder), 13+ (Blob constructor)
Safari 6+ (Blob constructor)
IE10 supports MSBlobBuilder and URL.createObjectURL. However, trying to create a Web Worker from a blob:-URL throws a SecurityError.
Opera 12 does not support URL API. Some users may have a fake version of the URL object, thanks to this hack in browser.js.
Fallback 1: data-URI
Opera supports data-URIs as an argument to the Worker constructor. Note: Do not forget to escape special characters (Such as # and %).
// response as defined in the first example
var worker = new Worker('data:application/javascript,' +
encodeURIComponent(response) );
// ... Test as defined in the first example
Demo: http://jsfiddle.net/uqcFM/37/
Fallback 2: Eval
eval can be used as a fallback for Safari (<6) and IE 10.
// Worker-helper.js
self.onmessage = function(e) {
self.onmessage = null; // Clean-up
eval(e.data);
};
// Usage:
var worker = new Worker('Worker-helper.js');
// `response` as defined in the first example
worker.postMessage(response);
// .. Test as defined in the first example
I agree with the current accepted answer but often editing and managing the worker code will be hectic as its in the form of a string.
So optionally we can use the below approach where we can keep the worker as a function, and then covert to string->blob:
// function to be your worker
function workerFunction() {
var self = this;
self.onmessage = function(e) {
console.log('Received input: ', e.data); // message received from main thread
self.postMessage("Response back to main thread");
}
}
///////////////////////////////
var dataObj = '(' + workerFunction + ')();'; // here is the trick to convert the above fucntion to string
var blob = new Blob([dataObj.replace('"use strict";', '')]); // firefox adds "use strict"; to any function which might block worker execution so knock it off
var blobURL = (window.URL ? URL : webkitURL).createObjectURL(blob, {
type: 'application/javascript; charset=utf-8'
});
var worker = new Worker(blobURL); // spawn new worker
worker.onmessage = function(e) {
console.log('Worker said: ', e.data); // message received from worker
};
worker.postMessage("some input to worker"); // Send data to our worker.
This is tested in IE11+ and FF and Chrome
The accepted answer is a bit complex, due to supporting backwards compatibility, so I wanted to post the same thing but simplified. Try this in your (modern) browser console:
const code = "console.log('Hello from web worker!')"
const blob = new Blob([code], {type: 'application/javascript'})
const worker = new Worker(URL.createObjectURL(blob))
// See the output in your console.
I've made an approach with most of your ideas and adding some of mine. The only thing my code needs on worker is to use 'this' to refer 'self' scope. I'm pretty sure that this is very improvable:
// Sample code
var code = function() {
this.onmessage = function(e) {
this.postMessage('Worker: '+e.data);
this.postMessage('Worker2: '+e.data);
};
};
// New thread worker code
FakeWorkerCode = function(code, worker) {
code.call(this);
this.worker = worker;
}
FakeWorkerCode.prototype.postMessage = function(e) {
this.worker.onmessage({data: e});
}
// Main thread worker side
FakeWorker = function(code) {
this.code = new FakeWorkerCode(code, this);
}
FakeWorker.prototype.postMessage = function(e) {
this.code.onmessage({data: e});
}
// Utilities for generating workers
Utils = {
stringifyFunction: function(func) {
// Stringify the code
return '(' + func + ').call(self);';
},
generateWorker: function(code) {
// URL.createObjectURL
windowURL = window.URL || window.webkitURL;
var blob, worker;
var stringified = Utils.stringifyFunction(code);
try {
blob = new Blob([stringified], {type: 'application/javascript'});
} catch (e) { // Backwards-compatibility
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
blob = new BlobBuilder();
blob.append(stringified);
blob = blob.getBlob();
}
if ("Worker" in window) {
worker = new Worker(windowURL.createObjectURL(blob));
} else {
worker = new FakeWorker(code);
}
return worker;
}
};
// Generate worker
var worker = Utils.generateWorker(code);
// Test, used in all examples:
worker.onmessage = function(e) {
alert('Response: ' + e.data);
};
function runWorker() {
worker.postMessage('working fine');
}
Demo: http://jsfiddle.net/8N6aR/
Nice answer - I've been working on a similar problem today when trying to create Web Workers with fallback capabilities when they're not available (i.e. run worker script in main thread). As this thread is pertains to the topic, I thought I'd provide my solution here:
<script type="javascript/worker">
//WORKER FUNCTIONS
self.onmessage = function(event) {
postMessage('Hello, ' + event.data.name + '!');
}
</script>
<script type="text/javascript">
function inlineWorker(parts, params, callback) {
var URL = (window.URL || window.webkitURL);
if (!URL && window.Worker) {
var worker = new window.Worker(URL.createObjectURL(new Blob([parts], { "type" : "text/javascript" })));
worker.onmessage = function(event) {
callback(event.data);
};
worker.postMessage(params);
} else {
var postMessage = function(result) {
callback(result);
};
var self = {}; //'self' in scope of inlineWorker.
eval(parts); //Converts self.onmessage function string to function on self via nearest scope (previous line) - please email chrisgwgreen.site#gmail.com if this could be tidier.
self.onmessage({
data: params
});
}
}
inlineWorker(
document.querySelector('[type="javascript/worker"]').textContent,
{
name: 'Chaps!!'
},
function(result) {
document.body.innerHTML = result;
}
);
</script>
</body>
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
// turn blocking pure function into a worker task
const functionFromPostRequest = task.wrap('function (exampleArgument) {}');
// run task on a autoscaling worker pool
functionFromPostRequest('exampleArgumentValue').then(result => {
// do something with result
});
Expanding on #Chanu_Sukarno's code, you can simply pass in a worker function (or string) to this function and it will execute it inside a web worker:
async function doWorkerTask(workerFunction, input, buffers) {
// Create worker
let fnString = '(' + workerFunction.toString().replace('"use strict";', '') + ')();';
let workerBlob = new Blob([fnString]);
let workerBlobURL = window.URL.createObjectURL(workerBlob, { type: 'application/javascript; charset=utf-8' });
let worker = new Worker(workerBlobURL);
// Run worker
return await new Promise(function(resolve, reject) {
worker.onmessage = function(e) { resolve(e.data); };
worker.postMessage(input, buffers);
});
}
Here's an example of how to use it:
function myTask() {
self.onmessage = function(e) {
// do stuff with `e.data`, then:
self.postMessage("my response");
self.close();
}
}
let output = await doWorkerTask(myTask, input, inputBuffers);
// now you can do something with `output` (which will be equal to "my response")
In nodejs, doWorkerTask looks like this:
async function doWorkerTask(workerFunction, input, buffers) {
let Worker = require('webworker-threads').Worker;
let worker = new Worker(workerFunction);
// Run worker
return await new Promise(function(resolve, reject) {
worker.onmessage = function(e) { resolve(e.data); };
worker.postMessage(input, buffers);
});
}
Here's an alternative approach that lets you define a worker using a function with optional setup arguments.
note - you can only use setup arguments that can be stringified - use worker.postMessage to send anything else
// define our worker function
function workerFunc(arg1,arg2) {
console.log("worker is running:"+arg1+" "+arg2);
postMessage(arg1+" "+arg2);
setInterval(sendTime,1000);
function sendTime(){
postMessage(new Date().toUTCString() );
}
}
// (optionally) define an element to inspect the script that worker will use
workerFunc.debug = document.getElementById("worker_script");
// start the worker and get replies from it
startWorker(workerFunc,"hello","world").addEventListener("message",function(e){
document.getElementById("worker_reply").innerHTML = e.data;
});
function startWorker (fn) {
const src = fn.toString();
const args = src.substring(src.indexOf("(")+1,src.indexOf(")"));
const code = ( args ? "let ["+args+"]="+JSON.stringify([].slice.call(arguments,1))+";\n" : "" )+ src.substring(src.indexOf("{")+1,src.length-1);
const blob = new Blob([code], {type: 'application/javascript'})
if (fn.debug) fn.debug.innerHTML=code;
return new Worker(URL.createObjectURL(blob))
}
<textarea id="worker_reply"></textarea>
<h1>Worker Script</h1>
<pre id="worker_script">
</pre>
Use my tiny plugin https://github.com/zevero/worker-create
var worker_url = Worker.create("self.postMessage('Example post from Worker');");
var worker = new Worker(worker_url);
But you may also give it a function.
My solution (can be "promise"d easily...)
function makeWorker(workerFunction, cb) {
// Create worker
var tplFun = "onmessage = function(e){console.log(e); var args = Array.prototype.slice.call(e.data); var res=___.apply(this,args);postMessage(res);}"
var fnTxt = workerFunction.toString().replace('"use strict";', '');
var final = tplFun.replace("___", fnTxt);
let workerBlob = new Blob([final]);
let workerBlobURL = window.URL.createObjectURL(workerBlob, { type: 'application/javascript; charset=utf-8' });
let worker = new Worker(workerBlobURL);
return function () {
var args = Array.prototype.slice.call(arguments);
console.log(args)
worker.postMessage(args);
worker.onmessage = function (e) {
console.log(e.data);
cb(e.data);
}
}
}
function myTask(a, b, c) {
return a * b * c;
}
function onresult(e) {
alert(e);
}
var fn = makeWorker(myTask, onresult)
fn(1, 2, 3);
Can be nice with "pure and slow" function !
Based on accepted answer, class to worker, interesting topic
// worker class
class SimpleWorker {
constructor() {
console.log("simple worker init");
}
onMessage(event) {
console.log("main to worker", event.data);
postMessage({
type: "answer",
data: "data from worker"
});
}
}
// class to worker
const workerFromClass = workerClassRef => {
console.log(workerClassRef.name, "to worker");
// factory method, converted to string, used to instanciate worker
let workerFactory = (self, workerClass) => {
let worker = new workerClass();
self["onmessage"] = worker.onMessage.bind(worker);
};
// compute worker code string
// worker class & factory function
let str = workerClassRef.toString() + "\n"
+ "(" + workerFactory.toString() + ")"
+ "(this," + workerClassRef.name + ");"
// worker code to blob
let blob = new Blob(
[str],
{type: "application/javascript"}
);
// return worker instance
return new Worker(
URL.createObjectURL(blob)
);
};
// main
// create worker
let worker = workerFromClass(SimpleWorker);
// handle messages from worker
worker.addEventListener(
"message",
event => console.log("worker to main", event.data)
);
// send message to worker
let message = {
type: "question",
data: "data from main"
};
console.log("main to worker", message);
worker.postMessage(message);
You can get real-data from the objectURL and not just blob by changing the responseType to either "text" or "arraybuffer".
Here is a back-and-forth conversion of text/javascript to blob to objectURL back to blob or text/javascript.
if you are wondering, I'm using it to generate a web-worker with no external files
you may use it to return binary content, for example a YouTube video ;) (from the <video> tag resource attribute)
var blob = new Blob(['self.onmessage=function(e){postMessage(e)}'],{type: 'text/javascript'}); //->console: (object) Blob {size: 42, type: "text/javascript", slice: function}
var obju = URL.createObjectURL(js_blob); //->console: "blob:http%3A//stackoverflow.com/02e79c2b-025a-4293-be0f-f121dd57ccf7"
var xhr = new XMLHttpRequest();
xhr.open('GET', 'blob:http%3A//stackoverflow.com/02e79c2b-025a-4293-be0f-f121dd57ccf7', true);
xhr.responseType = 'text'; /* or "blob" */
xhr.onreadystatechange = function(){
if(xhr.DONE !== xhr.readyState) return;
console.log(xhr.response);
}
xhr.send();
/*
responseType "blob" ->console: (object) Blob {size: 42, type: "text/javascript", slice: function}
responseType "text" ->console: (text) 'self.onmessage=function(e){postMessage(e)}'
*/