Skip to content

Instantly share code, notes, and snippets.

@jozefchutka
Last active June 13, 2024 07:34
Show Gist options
  • Select an option

  • Save jozefchutka/d6be7049f764c11dc059029908cb6ed9 to your computer and use it in GitHub Desktop.

Select an option

Save jozefchutka/d6be7049f764c11dc059029908cb6ed9 to your computer and use it in GitHub Desktop.
<script>
/*
thread() is a function that allows you to execute any isolated function asynchronously in a worker thread.
await thread(myFunction, [arg1, arg2, ...])
See more examples and tests below using async functions, transfers, aborts, concurrency,.
*/
function thread(func, args, options) {
return new Promise((resolve, reject) => {
function terminate() {
worker.terminate();
URL.revokeObjectURL(url);
}
const payload = `
onmessage = async ({data:{args, transferFromWorker}}) => {
const r = await (${func.toString()}).apply(this, args);
const t = transferFromWorker?.map(i => i.split(".").reduce((a, c) => a[c], r));
postMessage(r, t);
}
onunhandledrejection = onerror = e => {
e.preventDefault();
throw e.type;
}`;
const url = URL.createObjectURL(new Blob([payload], {type:"text/javascript"}));
const worker = new Worker(url);
worker.onmessage = ({data}) => {
terminate();
resolve(data);
}
worker.onerror = worker.onmessageerror = async () => {
terminate();
try {
resolve(await func.apply(this, args));
} catch(error) {
reject(error);
}
}
worker.postMessage({args, transferFromWorker:options?.transferFromWorker}, options?.transferToWorker);
const signal = options?.signal;
signal?.addEventListener("abort", () => {
terminate();
reject(signal.reason);
})
})
}
(async () => {
// TEST Simple function
const result1 = await thread((a, b, c) => a + b + c, [1, 2, 3]);
console.log("test1", result1);
// TEST Function that uses transfer
const test2 = source => {
const result = new Uint8Array(source.byteLength * 2);
result.set(source);
result.set(source, source.byteLength);
return {myWrapper:{nested:result}, source};
}
const array = new Uint8Array([1, 2, 3]);
const result2 = await thread(test2, [array], {
transferToWorker: [array.buffer],
transferFromWorker: ["myWrapper.nested.buffer", "source.buffer"]
})
console.log("test2", result2);
// TEST Async function
async function test3() {
await new Promise(r => setTimeout(r, 100));
return "asyncResult";
}
const result3 = await thread(test3);
console.log("test3", result3);
// TEST Cancellable function
async function test4() {
await new Promise(r => setTimeout(r, 5000));
return 789;
}
const controller = new AbortController();
setTimeout(() => controller.abort("took too long"), 100);
const result4 = await thread(test4, undefined, {signal:controller.signal})
.catch(reason => console.log("test4", `Expected abort ${reason === "took too long"}`));
// TEST Not executable in Worker
const test5 = () => document.documentElement.innerHTML;
const result5 = await thread(test5);
console.log("test5", `length=${result5.length}`);
// TEST Concurrency
async function test6(i) {
await new Promise(r => setTimeout(r, 100));
return i;
}
const result6 = await Promise.all([
thread(test6, [1]), thread(test6, [2]), thread(test6, [3]), thread(test6, [4]),
thread(test6, [5]), thread(test6, [6]), thread(test6, [7]), thread(test6, [8])]);
console.log("test6", result6);
})()
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment