|
1 | 1 | /** Move an async function into its own thread. |
2 | | - * @param {Function} fn The (async) function to run in a Worker. |
| 2 | + * @param {Function} asyncFunction An (async) function to run in a Worker. |
3 | 3 | */ |
4 | | -export default function greenlet(fn) { |
5 | | - let w = new Worker(URL.createObjectURL(new Blob([ |
6 | | - 'onmessage=('+( |
7 | | - f => ({ data }) => Promise.resolve().then( |
8 | | - () => f.apply(f, data[1]) |
9 | | - ).then( |
10 | | - d => { postMessage([data[0], null, d]); }, |
11 | | - e => { postMessage([data[0], ''+e]); } |
12 | | - ) |
13 | | - )+')('+fn+')' |
14 | | - ]))), |
15 | | - c = 0, |
16 | | - p = {}; |
17 | | - w.onmessage = ({ data: [c,e,d] }) => { |
18 | | - p[c][e?1:0](e||d); |
19 | | - delete p[c]; |
| 4 | +export default function greenlet(asyncFunction) { |
| 5 | + // Create an "inline" worker |
| 6 | + let worker = new Worker( |
| 7 | + // The URL is a pointer to a stringified function (as a blob object) |
| 8 | + URL.createObjectURL( |
| 9 | + new Blob([ |
| 10 | + // Register our wrapper function as the message handler |
| 11 | + 'onmessage=('+( |
| 12 | + // f() is the user-supplied async function |
| 13 | + userFunc => ({ data }) => Promise.resolve().then( |
| 14 | + // invoking within then() captures exceptions in f() as rejections |
| 15 | + () => userFunc.apply(userFunc, data[1]) |
| 16 | + ).then( |
| 17 | + d => { |
| 18 | + // success handler - callback(id, null, result) |
| 19 | + postMessage([data[0], null, d]); |
| 20 | + }, |
| 21 | + e => { |
| 22 | + // error handler - callback(id, err) |
| 23 | + postMessage([data[0], ''+e]); |
| 24 | + } |
| 25 | + ) |
| 26 | + )+')('+asyncFunction+')' // pass user-supplied function to the closure |
| 27 | + ]) |
| 28 | + ) |
| 29 | + ), |
| 30 | + |
| 31 | + // A simple counter is used to generate worker-global unique ID's for RPC: |
| 32 | + currentId = 0, |
| 33 | + |
| 34 | + // Outward-facing promises store their "controllers" (`[request, reject]`) here: |
| 35 | + promises = {}; |
| 36 | + |
| 37 | + // Handle RPC results/errors coming back out of the worker |
| 38 | + worker.onmessage = ({ data: [id, err, result] }) => { |
| 39 | + // invoke the promise's resolve() or reject() depending on whether there was an error. |
| 40 | + promises[id][err ? 1 : 0](err || result); |
| 41 | + // ... then delete the promise controller |
| 42 | + delete promises[id]; |
20 | 43 | }; |
21 | | - return (...a) => new Promise( (y, n) => { |
22 | | - p[++c] = [y, n]; |
23 | | - w.postMessage([c, a]); |
| 44 | + |
| 45 | + // Return a proxy function that forwards calls to the worker & returns a promise for the result. |
| 46 | + return (...args) => new Promise( (resolve, reject) => { |
| 47 | + promises[++currentId] = [resolve, reject]; |
| 48 | + // Send an RPC call to the worker - call(id, params) |
| 49 | + worker.postMessage([currentId, args]); |
24 | 50 | }); |
25 | 51 | } |
0 commit comments