-
Notifications
You must be signed in to change notification settings - Fork 88
Expand file tree
/
Copy pathindex.js
More file actions
113 lines (106 loc) · 3.26 KB
/
index.js
File metadata and controls
113 lines (106 loc) · 3.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/** TODO:
* - pooling (+ load balancing by tracking # of open calls)
* - queueing (worth it? sortof free via postMessage already)
*
* @example
* let worker = workerize(`
* export function add(a, b) {
* // block for a quarter of a second to demonstrate asynchronicity
* let start = Date.now();
* while (Date.now()-start < 250);
* return a + b;
* }
* `);
* (async () => {
* console.log('3 + 9 = ', await worker.add(3, 9));
* console.log('1 + 2 = ', await worker.add(1, 2));
* })();
*/
export default function workerize(code) {
let exports = {};
let exportsObjName = `__EXPORTS_${Math.random().toString().substring(2)}__`;
if (typeof code==='function') code = `(${toCode(code)})(${exportsObjName})`;
code = toCjs(code, exportsObjName, exports);
code += `\n(${toCode(setup)})(self, ${exportsObjName}, {})`;
let blob = new Blob([code], {
type: 'application/javascript'
}),
url = URL.createObjectURL(blob),
worker = new Worker(url),
counter = 0,
callbacks = {};
worker.kill = signal => {
worker.postMessage({ type: 'KILL', signal });
setTimeout(worker.terminate);
};
let term = worker.terminate;
worker.terminate = () => {
URL.revokeObjectURL(url);
term();
};
worker.rpcMethods = {};
function setup(ctx, rpcMethods, callbacks) {
/*
ctx.expose = (methods, replace) => {
if (typeof methods==='string') {
rpcMethods[methods] = replace;
}
else {
if (replace===true) rpcMethods = {};
Object.assign(rpcMethods, methods);
}
};
*/
ctx.addEventListener('message', ({ data }) => {
if (data.type!=='RPC') return;
let id = data.id;
if (id==null) return;
if (data.method) {
let method = rpcMethods[data.method];
if (method==null) {
ctx.postMessage({ type: 'RPC', id, error: 'NO_SUCH_METHOD' });
return;
}
Promise.resolve()
.then( () => method.apply(null, data.params) )
.then( result => { ctx.postMessage({ type: 'RPC', id, result }); })
.catch( error => { ctx.postMessage({ type: 'RPC', id, error }); });
return;
}
let callback = callbacks[id];
if (callback==null) throw Error(`Unknown callback ${id}`);
delete callbacks[id];
data.error
? callback.reject(Error(data.error))
: callback.resolve(data.result);
});
}
setup(worker, worker.rpcMethods, callbacks);
worker.call = (method, params) => new Promise( (resolve, reject) => {
let id = `rpc${++counter}`;
callbacks[id] = { method, resolve, reject };
worker.postMessage({ type: 'RPC', id, method, params });
});
for (let i in exports) {
if (exports.hasOwnProperty(i) && !(i in worker)) {
worker[i] = (...args) => worker.call(i, args);
}
}
return worker;
}
function toCode(func) {
return Function.prototype.toString.call(func);
}
function toCjs(code, exportsObjName, exports) {
exportsObjName = exportsObjName || 'exports';
exports = exports || {};
code = code.replace(/^(\s*)export\s+default\s+/m, (s, before) => {
exports.default = true;
return `${before}${exportsObjName}.default = `;
});
code = code.replace(/^(\s*)export\s+(function|const|let|var)(\s+)([a-zA-Z$_][a-zA-Z0-9$_]*)/m, (s, before, type, ws, name) => {
exports[name] = true;
return `${before}${exportsObjName}.${name} = ${type}${ws}${name}`;
});
return `var ${exportsObjName} = {};\n${code}\n${exportsObjName};`;
}