Skip to content

Instantly share code, notes, and snippets.

@BananaAcid
Last active October 5, 2024 19:51
Show Gist options
  • Save BananaAcid/b8e3d609c5655301eccaeb44c9df9037 to your computer and use it in GitHub Desktop.
Save BananaAcid/b8e3d609c5655301eccaeb44c9df9037 to your computer and use it in GitHub Desktop.
create an in-memory worker by passing a function or promise and be able to await its return-result, args must be serializable
/**
* create an in-memory worker by passing a function or promise and be able to await its return-result, args must be serializable
*
* @author Nabil Redmann
* @license MIT
*
* @see https://gist.github.com/BananaAcid/b8e3d609c5655301eccaeb44c9df9037
* @reference https://stackoverflow.com/a/19201292/1644202
*/
// fn can be function or async
// args are the params for the fn
// bound contexts are ignored (.toString() will not contain .bind() contexts)
// the worker always posts back if done! if something is returned from a the provided fn, it will be returned from callFnInThread().
//
// need other files or functions in the worker? just convert them to string (`.toString()`) and add them to the beginning of the blob as array values -- max size: https://stackoverflow.com/a/43816041/1644202
// or: your browser is updated to a current version, it will support module workers; {type: 'module'} and imports ... see: https://stackoverflow.com/a/45578811/1644202
// put the import statements as string to the blob array, preceeding the promise, or use `import(...)`
async function callFnInThread(fn, ...args) {
return await new Promise((resolve, reject) => {
let blobUrl = URL.createObjectURL( new Blob([`Promise.resolve( ${fn.toString()}.apply(self, ${JSON.stringify(args)}) ).then(r => self.postMessage(r))`]), {type: 'application/javascript'});
let worker = new Worker( blobUrl );
worker.onmessage = ev => {
URL.revokeObjectURL(blobUrl);
resolve(ev.data);
}
worker.onerror = err => {
URL.revokeObjectURL(blobUrl);
reject(err);
}
});
}
// some test fns
function test1() {
return 'done';
}
async function test2() {
await new Promise(r => setTimeout(r, 1000));
return 'done';
}
function test3(a) {
return 'done: ' + a;
}
async function test4(a, b) {
await new Promise(r => setTimeout(r, 1000));
return 'done: ' + a + ' --- ' + b;
}
function error1(a) {
return 'done: ' + a;
}
function error2() {
// do not return anything, works as well
}
function error3() {
throw new Error('Dang!');
}
// Tests
let result = await callFnInThread(test1);
console.log('RESULT', result);
let result2 = await callFnInThread(test2);
console.log('RESULT', result2);
let result3 = await callFnInThread(test3, 123);
console.log('RESULT', result3);
let result4 = await callFnInThread(test4, 123, 456);
console.log('RESULT', result4);
let resultErr1 = await callFnInThread(error1);
console.log('RESULT', resultErr1); // done: undefined
let resultErr2 = await callFnInThread(error2);
console.log('RESULT', resultErr2); // undefined
let resultErr3 = await callFnInThread(error3);
console.log('RESULT', resultErr3); // triggers uncought error, no console.log is not reached
let resultErr3_2 = await callFnInThread(error3).catch(e => null);
console.log('RESULT', resultErr3_2); // null ---> cought, but show the original uncought error from the web worker in the console
// ----------------------------------------------------------------------------------------
// would not be able to receive result! Is just for fireing them off
let fn = test1;
navigator.serviceWorker.controller.postMessage([
'dyn_workers/' + (fn.prototype.name || 'fn' + Date.now()) + '.js',
`Promise.resolve(${fn.toString()})` // resolves functions and asyncs
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment