diff --git a/LICENSE b/LICENSE index ec9710e..9ab906d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Elias +Copyright (c) 2019-2020 Elias Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 77f0e97..8dfd29f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ # parry -A simple way of running functions in their own WebWorker. Mainly for deno + +A simple way of running functions in their own WebWorker that works with +[deno](https://deno.land/). + +## Usage + +The following function will run in it's own WebWorker, enabling threading and +therefor not blocking the main event loop. This becomes very useful for +parallelization of certain computations or just a way of running a function and +not blocking the main event loop while it runs. + +```ts +import { parry } from "https://deno.land/x/parry/mod.ts"; + +async function countSerial(limit: number): Promise { + for (let i = 0; i < limit; i++) {} +} + +console.time("4x CountSerial"); +await Promise.all([ + countSerial(1e9), + countSerial(1e9), + countSerial(1e9), + countSerial(1e9), +]); +console.timeEnd("4x CountSerial"); + +const threads = [ + parry(countSerial), + parry(countSerial), + parry(countSerial), + parry(countSerial), +]; + +console.time("4x CountParallel"); +await Promise.all([ + threads[0](1e9), + threads[1](1e9), + threads[2](1e9), + threads[3](1e9), +]); +console.timeEnd("4x CountParallel"); + +parry.close(); +``` + +The parallelized version is 4x faster than the serial version of the count +function: + +``` +4x CountSerial: 1885ms +4x CountParallel: 509ms +``` diff --git a/mod.ts b/mod.ts index 659f762..57c7086 100644 --- a/mod.ts +++ b/mod.ts @@ -1,11 +1,24 @@ +class ParryError extends Error { + constructor(message: string = "") { + super(message); + this.name = this.constructor.name; + this.stack; + } +} + type AsyncFunction = (...args: S) => Promise; type MaybeAsyncFunction = (...args: S) => T | Promise; interface ParryFunction extends AsyncFunction { + id: number; + closed: boolean; close: () => void; set: (f: MaybeAsyncFunction) => void; } +const funcs: Map> = new Map(); +let funcsIndex: number = 0; + export function parry( original: (...args: S) => T | Promise, ): ParryFunction { @@ -36,7 +49,11 @@ export function parry( delete promises[id]; }; - const call: ParryFunction = (...args: S[]): Promise => { + const func: ParryFunction = (...args: S[]): Promise => { + if (func.closed) { + throw new ParryError("Cannot run closed WebWorker"); + } + return new Promise((resolve, reject) => { promises[id] = [resolve, reject]; worker.postMessage({ @@ -49,20 +66,41 @@ export function parry( }); }; - call.close = (): void => { + func.id = funcsIndex++; + func.closed = false; + + func.close = (): void => { + if (func.closed) { + throw new ParryError("Cannot close already closed WebWorker"); + } + worker.postMessage({ type: "close" }); + func.closed = true; + funcs.delete(func.id); }; - call.set = (f: MaybeAsyncFunction): void => { + func.set = (f: MaybeAsyncFunction): void => { + if (func.closed) { + throw new ParryError("Cannot set closed WebWorker"); + } + worker.postMessage({ type: "set", data: f.toString(), }); }; - call.set(original); + func.set(original); + + funcs.set(func.id, func); - return call; + return func; } +parry.close = (): void => { + for (const [id, func] of funcs) { + func.close(); + } +}; + export default parry;