Skip to content

Instantly share code, notes, and snippets.

@getify
Last active April 20, 2024 11:35
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save getify/1b26accb1a09aa53ad25 to your computer and use it in GitHub Desktop.
Save getify/1b26accb1a09aa53ad25 to your computer and use it in GitHub Desktop.
first draft sketch of a "Worker" polyfill
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Worker Polyfill</title>
<script src="polyfill.worker.js"></script>
</head>
<body>
<h1>Worker Polyfill</h1>
<script>
var w = new Worker("child.js");
w.addEventListener("message",function(evt){
console.log("from test.html:",evt.data);
});
w.postMessage("hello world");
// from child.js: hello world
// from test.html: HELLO WORLD
</script>
</body>
</html>
// child.js
var self = this;
addEventListener("message",function(evt){
console.log("from child.js:",evt.data);
// here `self.` is optional
self.postMessage(evt.data.toUpperCase());
});
// polyfill.worker.js
(function WorkerPolyfill(){
function DualEventers() {
var cycle, scheduling_queue,
timer = (typeof setImmediate !== "undefined") ?
function timer(fn) { return setImmediate(fn); } :
setTimeout
;
// Note: using a queue instead of array for efficiency
function Queue() {
var first, last, item;
function Item(fn) {
this.fn = fn;
this.next = void 0;
}
return {
add: function add(fn) {
item = new Item(fn);
if (last) {
last.next = item;
}
else {
first = item;
}
last = item;
item = void 0;
},
drain: function drain() {
var f = first;
first = last = cycle = null;
while (f) {
f.fn();
f = f.next;
}
}
};
}
scheduling_queue = Queue();
function schedule(fn) {
scheduling_queue.add(fn);
if (!cycle) {
cycle = timer(scheduling_queue.drain);
}
}
function Eventer(handlers) {
function addEventListener(evtname,handler) {
if (handlers.local) {
handlers.local[evtname] = handlers.local[evtname] || [];
handlers.local[evtname].push(handler);
}
}
function notifyRemote(evtname,data) {
if (evtname != "error") {
data = { data: data };
}
if (typeof handlers.remoteEventer["on" + evtname] == "function") {
handlers.remoteEventer["on" + evtname](data);
}
handlers.remote[evtname].forEach(function foreach(h){
h(data);
});
}
function postMessage(data) {
if (buffer) {
buffer.push(data);
return;
}
schedule(function scheduler(){
notifyRemote("message",data);
});
}
function unbuffer() {
if (buffer && buffer.length > 0) {
buffer.forEach(function foreach(item){
notifyRemote("message",item);
});
}
buffer = null;
}
var buffer = [];
this.onmessage = null;
this.onerror = null;
this.addEventListener = addEventListener;
this.postMessage = postMessage;
this.unbuffer = unbuffer;
this.notifyRemote = notifyRemote;
}
var e1, e2,
h1 = {
local: { message: [], error: [] }
},
h2 = {
local: { message: [], error: [] }
}
;
// wire the duals' handlers together
h1.remote = h2.local;
h2.remote = h1.local;
// create the dual eventers
e1 = new Eventer(h1);
e2 = new Eventer(h2);
h1.remoteEventer = e2;
h2.remoteEventer = e1;
return [e1,e2];
}
function importScripts() {
throw "importScripts() not supported by this polyfill.";
}
function publicEventerAPI(e) {
this.onmessage = e.onmessage;
this.onerror = e.onerror;
this.addEventListener = e.addEventListener;
this.postMessage = e.postMessage;
}
if (!window.Worker) {
window.Worker = function window$Worker(url) {
var eventers = DualEventers(), e1 = eventers[0],
e2 = eventers[1], worker, xhr,
unbufferE1 = e1.unbuffer, unbufferE2 = e2.unbuffer,
notifyE2 = e1.notifyRemote, notifyE1 = e2.notifyRemote,
globalPropNames = [], globalProps
;
// clean up public APIs
publicEventerAPI.call(this,e1);
e2 = new publicEventerAPI(e2);
// setup fake `self` global properties
e2.importScripts = importScripts;
e2.self = e2;
e2.navigator = window.navigator;
e2.location = { href: url };
e2.XMLHttpRequest = window.XMLHttpRequest;
e2.WebSocket = window.WebSocket;
// prepare for worker fake Function
globalPropNames = Object.getOwnPropertyNames(e2);
globalProps = globalPropNames.map(function map(name){
return e2[name];
});
// inside eventer doesn't need to be buffered
unbufferE2();
// load the worker file
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function onready() {
if (xhr.readyState == 4) {
worker = Function.apply(
null,
globalPropNames.concat([xhr.responseText])
);
try {
worker.apply(e2,globalProps);
// unbuffer outside eventer now
unbufferE1();
}
catch (err) {
notifyE1("error",err);
}
}
};
xhr.open( "GET", url );
xhr.send();
};
}
})();
@RyanCCollins
Copy link

Impressed

@Ultihamed
Copy link

Ultihamed commented Aug 4, 2020

Great

@Hamidalavi
Copy link

clap

@yibingxiong
Copy link

@victorfds
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment