Intro
At the highest level of its design, Concurrent.js is a module loader like require and import, but instead of loading a module into the main thread, it loads the module into a worker. It injects the concurrent behavior into the imported classes and functions so they can be used as usual. Concurrent.js works on Node.js, Deno, and web browsers.
Important notes
- This is a helper library not a new implementation of Web Workers.
- This is an early version of the library and must not be used in a real project.
Features
- Supporting Node.js
- Supporting browser
- Supporting Deno
- Executing JavaScript (ECMAScript & CommonJS)
- Executing TypeScript
- Executing WebAssembly
- Accessing exported functions
- Accessing exported classes
- Instantiation
- Instance members
- Fields
- Getters and setters
- Methods
- Static members
- Fields
- Getters and setters
- Methods
- Parallel execution
- Reactive concurrency
- Inter-worker communication
- Event sourcing
- Data sharing
- Dependency injection
- Sandboxing
- Language interoperability (Server-side)
- C
- Rust
- Python
Technical facts
- Built upon web workers (a.k.a. worker threads).
- Creates a worker once and reuses it.
- Automatically cleans up a workerβs memory.
- Automatically creates and terminates workers.
- Has no runtime dependency.
- Written in TypeScript with the strictest ESNext config.
- Strictly designed to support strongly-typed programming.
- Packaged as platform-specific bundles that target ES2020.
Hello World!
Save and run the hello world script to see it in action:
bash hello_world.shUsage
// load a module into a worker
const { SampleObject, sampleFunction } = await concurrent.module('sample-module').load()
// access a function
const result = await sampleFunction(/*...args*/) // call the function
// access a class
const obj = await new SampleObject(/*...args*/) // instantiate
const value = await obj.sampleProp // get a field or getter
await ((obj.sampleProp = 1), obj.sampleProp) // set a field or setter
const result = await obj.sampleMethod(/*...args*/) // call a method
// access static members of a class
const value = await SampleObject.sampleStaticProp // get a static field or getter
await ((SampleObject.sampleStaticProp = 1), SampleObject.sampleStaticProp) // set a static field or setter
const result = await SampleObject.sampleStaticMethod(/*...args*/) // call a static method
// terminate Concurrent.js
await concurrent.terminate()Sample
Node.js (ECMAScript)
npm i @bitair/concurrent.js@latestindex.js
import { concurrent } from '@bitair/concurrent.js'
const { factorial } = await concurrent.module('extra-bigint').load()
const result = await factorial(50n)
console.log(result)
await concurrent.terminate()package.json
{
"type": "module",
"dependencies": {
"@bitair/concurrent.js": "^0.5.13",
"extra-bigint": "^1.1.10"
}
}node .Deno
index.ts
import { concurrent } from 'https://deno.land/x/concurrentjs@v0.5.13/mod.ts'
const { factorial } = await concurrent.module(new URL('services/index.ts', import.meta.url)).load()
const result = await factorial(50n)
console.log(result)
await concurrent.terminate()services/index.ts
export { factorial } from 'extra-bigint'deno.json
{
"imports": {
"extra-bigint": "npm:extra-bigint@^1.1.10"
}
}deno run --allow-read --allow-net index.tsBrowser
.
βββ src
βββ services
βββ index.js
βββ app.js
βββ worker_script.js
.
βββ static
βββ index.html
.app.js
import { concurrent } from '@bitair/concurrent.js'
const { factorial } = await concurrent.module(new URL('services/index.js', import.meta.url)).load()
const result = await factorial(50n)
console.log(result)
await concurrent.terminate()services/index.js
export { factorial } from 'extra-bigint'worker_script.js
import '@bitair/concurrent.js/worker_script'index.html
<!DOCTYPE html>
<html>
<body>
<script type="module" src="scripts/main.js"></script>
</body>
</html>build.sh
#!/bin/bash
npx esbuild src/app.js --bundle --format=esm --platform=browser --target=esnext --outfile=static/scripts/main.js
npx esbuild src/worker_script.js --bundle --format=esm --platform=browser --target=esnext --outfile=static/scripts/worker_script.js
npx esbuild src/services/index.js --bundle --format=esm --platform=browser --target=esnext --outfile=static/scripts/services/index.jspackage.json
{
"type": "module",
"dependencies": {
"@bitair/concurrent.js": "^0.5.13",
"http-server": "^14.1.1",
"extra-bigint": "^1.1.10"
},
"devDependencies": {
"esbuild": "^0.17.8"
}
}bash ./build.sh && npx http-server staticBase URL
Concurrent.js uses the import.meta.url property as the base URL to resolve the scripts. Itβs also possible to provide a custom base URL:
npx esbuild src/app.js --target=es6 --define:process.env.BASE_URL=\"http://127.0.0.1:8080/scripts/\" --bundle --format=esm --platform=browser --outfile=static/scripts/main.jsParallelism
import { concurrent } from '@bitair/concurrent.js'
const extraBigint = concurrent.module('extra-bigint')
concurrent.config({ maxThreads: 16 }) // Instead of a hardcoded value use os.availableParallelism() in Node.js v19.4.0 or later
const ops = []
for (let i = 0; i <= 100; i++) {
const { factorial } = await extraBigint.load()
ops.push(factorial(i))
}
const results = await Promise.all(ops)
// ...rest of the code
await concurrent.terminate()API
concurrent.load<T>(src: string | URL) : TLoads the specified module into a worker.
src: stringThe path or URL of the loading module. The value of this parameter must be either an absolute path/URL or a package name.
concurrent.config(settings: ConcurrencySettings): voidConfigs the global settings of Concurrent.js
settings: ConcurrencySettingssettings.disabled: boolean [default=false]Setting it would disable Concurrent.js without the requirement to change any other code.
settings.maxThreads: number [default=1]The max number of available threads to be spawned.
settings.threadIdleTimeout: number | typeof Infinity [default=Infinity]Number of minutes that Concurrent.js would be waiting before terminating an idle thread.
settings.minThreads: number [default=0]The number of threads that must be created when Concurrent.js starts and also kept from being terminated when are idle.
concurrent.terminate(force?: boolean): Promise<void>Terminates Concurrent.js
force?: boolean [Not implemented]Forces Concurrent.js to exit immediately without waiting for workers to finish their tasks.