diff --git a/README.md b/README.md index f835ad6..a78d930 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ 3. Allows you to Thread already existing functions 5. Allows module imports inside the worker -## Example +## Examples +> See examples folder for more examples + ```typescript let thread = new Thread((e: MessageEvent)=>{ console.log('Worker: Message received from main script'); @@ -44,6 +46,22 @@ new Thread(someFunction, "module"); // thread an already existing function new Thread(someFunction, "module", ['import Something from "../some.bundle.js";']); // thread with custom importing ``` +**Async support** +```TypeScript +const thread = new Thread(async (_) => { + console.log("Worker: Message received from main script"); + // Some async logic... + await new Promise((ir) => setTimeout(ir, 2000)); + return "DONE"; +}, "module"); + +thread.onMessage((e) => { + console.log(`recived back from thread: ${e}`); +}); + +thread.postMessage(0); +``` + ## API ### Standard API diff --git a/Thread.bundle.js b/Thread.bundle.js index 4bab676..152f5cf 100644 --- a/Thread.bundle.js +++ b/Thread.bundle.js @@ -1,10 +1,14 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file +// This code was bundled using `deno bundle` and it's not recommended to edit it manually + class Thread { worker; imports; blob; blobURL = ""; stopped = false; - constructor(operation, type1, imports){ + constructor(operation, type, imports){ imports?.forEach((v)=>{ if (v.endsWith(".ts'") || v.endsWith('.ts"')) { throw new Error("Threaded imports do no support typescript files"); @@ -12,9 +16,7 @@ class Thread { }); this.imports = imports || []; this.blob = this.populateFile(operation); - this.blob.then(async (b)=>console.log(await b.text()) - ); - this.worker = this.makeWorker(type1); + this.worker = this.makeWorker(type); } async makeWorker(type) { this.blobURL = URL.createObjectURL(await this.blob); @@ -23,39 +25,45 @@ class Thread { }); } async populateFile(code) { - let imported = this.imports?.flatMap(async (val)=>(await this.copyDep(val)).join("\n") - ); + const imported = this.imports?.flatMap(async (val)=>(await this.copyDep(val)).join("\n")); return new Blob([ - `\n ${(await Promise.all(imported)).join("\n")}\n \n var global = {};\n var userCode = ${code.toString()}\n \n onmessage = function(e) {\n postMessage(userCode(e, global));\n }\n \n ` + ` + ${(await Promise.all(imported)).join("\n")} + + var global = {}; + var userCode = ${code.toString()} + + onmessage = async function(e) { + postMessage(await userCode(e, global)); + } + + ` ]); } async copyDep(str) { - var importPathRegex = /('|"|`)(.+\.js)(\1)/ig; - var importInsRegex = /(import( |))({.+}|.+)(from( |))/ig; - var matchedPath = importPathRegex.exec(str) || ""; - var file = false; - var fqfn = ""; + const importPathRegex = /('|"|`)(.+\.js)(\1)/ig; + const importInsRegex = /(import( |))({.+}|.+)(from( |))/ig; + const matchedPath = importPathRegex.exec(str) || ""; + let file = false; + let fqfn = ""; if (!matchedPath[0].includes("http://") && !matchedPath[0].includes("https://")) { file = true; fqfn = matchedPath[0].replaceAll(/('|"|`)/ig, ""); } - var matchedIns = importInsRegex.exec(str) || ""; + const matchedIns = importInsRegex.exec(str) || ""; if (!matchedIns) { throw new Error("The import instruction seems to be unreadable try formatting it, for example: \n" + "import { something } from './somet.js' \n "); } if (file) { - let x = await import(fqfn); - return Object.keys(x).map((v)=>x[v].toString() - ); + const x = await import(fqfn); + return Object.keys(x).map((v)=>x[v].toString()); } else { - let x = await import(matchedPath[0].replaceAll(/'|"/g, "")); - return Object.keys(x).map((v)=>x[v].toString() - ); + const x1 = await import(matchedPath[0].replaceAll(/'|"/g, "")); + return Object.keys(x1).map((v)=>x1[v].toString()); } } postMessage(msg) { - this.worker.then((w)=>w.postMessage(msg) - ); + this.worker.then((w)=>w.postMessage(msg)); return this; } async stop() { @@ -67,8 +75,7 @@ class Thread { URL.revokeObjectURL(this.blobURL); } onMessage(callback) { - this.worker.then((w)=>w.onmessage = (e)=>callback(e.data) - ); + this.worker.then((w)=>w.onmessage = (e)=>callback(e.data)); return this; } } diff --git a/Thread.ts b/Thread.ts index 216a3d1..7c86bcf 100644 --- a/Thread.ts +++ b/Thread.ts @@ -1,8 +1,13 @@ -export default class Thread { +/** + * > Type T -> return type + * + * > Type K -> data type of MessageEvent + */ +export default class Thread { public worker: Promise; private imports: Array; private blob: Promise; - private blobURL: string = ""; + private blobURL= ""; /** * Tells if the worker has been stopped */ @@ -13,7 +18,7 @@ export default class Thread { * @param imports Modules to import in the worker. only JS files allowed (over the net import allowed) */ constructor( - operation: (e: MessageEvent, globalObject?:{}) => T, + operation: (e: MessageEvent, globalObject?: Record) => T | Promise, type?: "classic" | "module", imports?: Array, ) { @@ -24,7 +29,6 @@ export default class Thread { }); this.imports = imports || []; this.blob = this.populateFile(operation); - this.blob.then(async (b)=>console.log(await b.text())); this.worker = this.makeWorker(type); } @@ -38,16 +42,17 @@ export default class Thread { ); } + // deno-lint-ignore ban-types private async populateFile(code: Function) { - let imported = this.imports?.flatMap(async (val) => (await this.copyDep(val)).join("\n")); + const imported = this.imports?.flatMap(async (val) => (await this.copyDep(val)).join("\n")); return new Blob([` ${(await Promise.all(imported)).join("\n")} var global = {}; var userCode = ${code.toString()} - onmessage = function(e) { - postMessage(userCode(e, global)); + onmessage = async function(e) { + postMessage(await userCode(e, global)); } `]); @@ -58,11 +63,11 @@ export default class Thread { * @param str the import line (eg: import {som} from "lorem/ipsum.js";) */ private async copyDep(str: string) { - var importPathRegex = /('|"|`)(.+\.js)(\1)/ig; // for the path string ("lorem/ipsum.js") - var importInsRegex = /(import( |))({.+}|.+)(from( |))/ig; // for the instruction before the path (import {som} from) - var matchedPath = importPathRegex.exec(str) || ""; - var file = false; - var fqfn = ""; + const importPathRegex = /('|"|`)(.+\.js)(\1)/ig; // for the path string ("lorem/ipsum.js") + const importInsRegex = /(import( |))({.+}|.+)(from( |))/ig; // for the instruction before the path (import {som} from) + const matchedPath = importPathRegex.exec(str) || ""; + let file = false; + let fqfn = ""; if ( !matchedPath[0].includes("http://") && @@ -71,7 +76,7 @@ export default class Thread { file = true; fqfn = matchedPath[0].replaceAll(/('|"|`)/ig, ""); } - var matchedIns = importInsRegex.exec(str) || ""; // matchedIns[0] > import {sss} from + const matchedIns = importInsRegex.exec(str) || ""; // matchedIns[0] > import {sss} from if (!matchedIns) { throw new Error( @@ -82,10 +87,10 @@ export default class Thread { if (file) { - let x = await import(fqfn); //Deno.realPathSync(fqfn) + const x = await import(fqfn); //Deno.realPathSync(fqfn) return Object.keys(x).map((v)=>x[v].toString()) } else { - let x = await import(matchedPath[0].replaceAll(/'|"/g,"")); + const x = await import(matchedPath[0].replaceAll(/'|"/g,"")); return Object.keys(x).map((v)=>x[v].toString()) } } @@ -94,7 +99,7 @@ export default class Thread { * Sends data to the Thread * @param msg */ - public postMessage(msg: any): this { + public postMessage(msg: K): this { this.worker.then(w=>w.postMessage(msg)); return this; } diff --git a/egg.json b/egg.json index 6994d88..f411f6e 100644 --- a/egg.json +++ b/egg.json @@ -4,8 +4,8 @@ "stable": true, "homepage": "https://github.com/duart38/Thread", "entry": "./Thread.ts", - "version": "3.0.0", - "releaseType": "patch", + "version": "4.0.0", + "releaseType": "major", "files": [ "./LICENSE", "./README.md", diff --git a/examples/example_allot_of_threads.ts b/examples/example_allot_of_threads.ts index 94721de..dcef548 100644 --- a/examples/example_allot_of_threads.ts +++ b/examples/example_allot_of_threads.ts @@ -1,11 +1,10 @@ import Thread from "../Thread.ts"; -let count = 13; +const count = 13; -function postMessage(e: any) {} +function postMessage(_e: unknown) {} function tester() { - let i = 0; setInterval(() => { postMessage(0); }, 500); @@ -14,6 +13,6 @@ function tester() { } for (let i = 0; i < count; i++) { - new Thread(tester, "module").onMessage((d) => console.log(`thread -> ${i}`)) + new Thread(tester, "module").onMessage((_) => console.log(`thread -> ${i}`)) .postMessage(0); } diff --git a/examples/example_async_support.ts b/examples/example_async_support.ts new file mode 100644 index 0000000..4f46df9 --- /dev/null +++ b/examples/example_async_support.ts @@ -0,0 +1,26 @@ +import Thread from "../Thread.ts"; + +/** + * Thanks to @praswicaksono for the suggestion + * -> https://github.com/praswicaksono + * -> https://github.com/duart38/Thread/issues/3 + */ + +const thread = new Thread(async (e) => { + console.log("Worker: Message received from main script"); + const result = e.data[0] * e.data[1]; + await new Promise((resolve) => setTimeout(resolve, 5 * 1000)) + if (isNaN(result)) { + return 0; + } else { + console.log("Worker: Posting message back to main script"); + return (result); + } +}, "module"); + +thread.onMessage((e) => { + console.log(`recived back from thread: ${e}`); +}); + +thread.postMessage([10, 12]); +thread.postMessage([10, 10]); diff --git a/examples/example_calculateprimes.ts b/examples/example_calculateprimes.ts index f8f037f..a2fdc46 100644 --- a/examples/example_calculateprimes.ts +++ b/examples/example_calculateprimes.ts @@ -1,18 +1,18 @@ import Thread from "../Thread.ts"; -let count = 2; // number of threads to spawn +const count = 2; // number of threads to spawn -function postMessage(e: any) {} // stops the compiler from complaining that the method is not available.. this gets pasted in the worker +function postMessage(_e: unknown) {} // stops the compiler from complaining that the method is not available.. this gets pasted in the worker function tester() { function calculatePrimes() { const iterations = 50; const multiplier = 100000000000; - var primes = []; - for (var i = 0; i < iterations; i++) { - var candidate = i * (multiplier * Math.random()); - var isPrime = true; - for (var c = 2; c <= Math.sqrt(candidate); ++c) { + const primes = []; + for (let i = 0; i < iterations; i++) { + const candidate = i * (multiplier * Math.random()); + let isPrime = true; + for (let c = 2; c <= Math.sqrt(candidate); ++c) { if (candidate % c === 0) { // not prime isPrime = false; diff --git a/examples/example_deno_worker.ts b/examples/example_deno_worker.ts index 489ef40..1fc9978 100644 --- a/examples/example_deno_worker.ts +++ b/examples/example_deno_worker.ts @@ -1,9 +1,9 @@ import Thread from "../Thread.ts"; import Observe from "https://raw.githubusercontent.com/duart38/Observe/master/Observe.ts"; -let tr = new Thread( +const tr = new Thread( (e) => { - let t = new Observe(e.data); // observable values + const t = new Observe(e.data); // observable values return t.getValue(); }, "module", diff --git a/examples/example_importing.ts b/examples/example_importing.ts index 896e5bc..a177d97 100644 --- a/examples/example_importing.ts +++ b/examples/example_importing.ts @@ -1,8 +1,8 @@ import Thread from "../Thread.ts"; import { CallMe } from "../test_import.js"; -let tr = new Thread( - (e) => { +const tr = new Thread( + (_e) => { CallMe(); return "pong"; }, diff --git a/examples/example_simple.ts b/examples/example_simple.ts index c28476f..6b7cca0 100644 --- a/examples/example_simple.ts +++ b/examples/example_simple.ts @@ -1,6 +1,6 @@ import Thread from "../Thread.ts"; -let thread = new Thread((e: MessageEvent) => { +const thread = new Thread((e) => { console.log("Worker: Message received from main script"); const result = e.data[0] * e.data[1]; if (isNaN(result)) { diff --git a/test.ts b/test.ts index e5d05e0..a113115 100644 --- a/test.ts +++ b/test.ts @@ -1,15 +1,14 @@ import { assertEquals, - assertThrows, - fail, + assertThrows } from "https://deno.land/std@0.90.0/testing/asserts.ts"; import Thread from "./Thread.ts"; import { returnNumber } from "./test_import.js"; Deno.test("incorrect file extension throws error", (): void => { assertThrows(() => { - let tr = new Thread( - (e) => { + const _tr = new Thread( + (_) => { return 1; }, "module", @@ -19,11 +18,25 @@ Deno.test("incorrect file extension throws error", (): void => { }); Deno.test("Worker takes in external function", async () => { - let run = new Promise((resolve) => { + const run = new Promise((resolve) => { function testfunc() { return 1; } - let t = new Thread(testfunc, "module"); + const t = new Thread(testfunc, "module"); + t.onMessage((n) => { + t.remove()?.then(() => resolve(n)); + }); + t.postMessage(2); + }); + assertEquals(await run, 1); +}); + +Deno.test("Worker async function supported", async () => { + const run = new Promise((resolve) => { + const t = new Thread(async ()=>{ + await new Promise((ir) => setTimeout(ir, 1000)) + return 1; + }, "module"); t.onMessage((n) => { t.remove()?.then(() => resolve(n)); }); @@ -33,8 +46,8 @@ Deno.test("Worker takes in external function", async () => { }); Deno.test("Command/Method chaining works", async () => { - let run = new Promise((resolve) => { - let t = new Thread((e) => 0, "module"); + const run = new Promise((resolve) => { + const t = new Thread((_) => 0, "module"); t.onMessage((n) => { t.remove()?.then(() => resolve(n)); }); @@ -44,8 +57,8 @@ Deno.test("Command/Method chaining works", async () => { }); Deno.test("Worker returns message", async () => { - let run = new Promise((resolve) => { - let t = new Thread((e) => e.data, "module"); + const run = new Promise((resolve) => { + const t = new Thread((e) => e.data, "module"); t.onMessage((n) => { t.remove()?.then(() => resolve(n)); }); @@ -56,8 +69,8 @@ Deno.test("Worker returns message", async () => { Deno.test("Local file imports work", async () => { - let run = new Promise((resolve) => { - let t = new Thread((e) => returnNumber(), "module", [ + const run = new Promise((resolve) => { + const t = new Thread((_) => returnNumber(), "module", [ 'import {returnNumber} from "./test_import.js"', ]); t.onMessage((n) => { @@ -68,9 +81,9 @@ Deno.test("Local file imports work", async () => { assertEquals(await run, 1); }); -Deno.test("Over the new file imports work", async () => { - let run = new Promise((resolve) => { - let t = new Thread((e) => returnNumber(), "module", [ +Deno.test("Over the network file imports work", async () => { + const run = new Promise((resolve) => { + const t = new Thread((_) => returnNumber(), "module", [ 'import { returnNumber } from "https://raw.githubusercontent.com/duart38/Thread/master/test_import.js"', ]); t.onMessage((n) => { @@ -82,8 +95,9 @@ Deno.test("Over the new file imports work", async () => { }); Deno.test("Worker has global object", async () => { - let run = new Promise<{} | undefined>((resolve) => { - let t = new Thread((e, glob) => glob, "module"); + // deno-lint-ignore ban-types + const run = new Promise<{} | undefined>((resolve) => { + const t = new Thread((_, glob) => glob, "module"); t.onMessage((n) => { t.remove()?.then(() => resolve(n)); });