Package Exports
- @bitair/concurrent.js
- @bitair/concurrent.js/worker_script
Readme
NOT READY FOR PRODUCTION
Intro
Concurrent.js helps with concurrent computation in JavaScript runtime environments. It can run CPU-intensive operations without blocking the main thread (i.e. the event loop or a browser tab/window). Concurrent.js is designed to load a JavaScript module with its dependencies into workers and make the module's exports accessible as usual. It provides a "write less, do more" isomorphic API that works on Node.js, Deno, and web browsers.
Features
- Loading CommonJS modules into workers
- Loading ECMAScript modules
- Accessing exported functions
- Accessing exported classes
- Instantiation
- Instance members
- Methods
- Getters and setters
- Fields
- Static members
- Methods
- Getters and setters
- Fields
- Loading WebAssembly
- Supporting Node.js
- Supporting web browsers
- Supporting Deno
- Reactive multithreading
- Sandboxing
Install
npm i -S @bitair/concurrent.js
Hello World!
Let's assume that we have a math module that potentially can block the main thread:
// math.js
export class Arithmetic {
isPrime(n) {
return isPrime(n)
}
// ...other methods
}
function isPrime(n) {
const j = Math.sqrt(n)
for (let i = 2; i <= j; i++) {
if (n % i === 0) return false
}
return n > 1
}
// ...other exported classes and functions
In order to run it without blocking the main thread:
Use the
load
method instead ofrequire
orimport
functions:const { Arithmetic } = await concurrent.load(MATH_MODULE_SRC) // MATH_MODULE_SRC must be an absolute path or URL
Instantiate any of the exported classes in the usual way and invoke their methods (must be done asynchronously, e.g. using the
await
operator). Instances must be explicitly deleted using thedispose
method:const arithmetic = new Arithmetic() const result = await arithmetic.isPrime(123456789) // ...rest of the code await concurrent.dispose(arithmetic)
Import and invoke exported functions (must be done asynchronously (e.g. using the
await
operator) :const { isPrime } = await concurrent.load(MATH_MODULE_SRC) const result = await isPrime(123456789) // ...rest of the code
Terminate Concurrent.js before exiting the app:
await concurrent.terminate()
Client-side usage
A client-side app should consist of three scripts/bundles:
One that contains the app's code
Another one that contains the concurrent modules' code
A
worker_script.js
bundle that must be generated from the following code:import '@bitair/concurrent.js/worker_script'
Please see the sample projects for a complete code. In order to run a sample project:
cd SAMPLE_DIR
npm i
npm run build
npm start
Testing usage
Using Concurrent.js in a test environment would slow down the testing process since instantiating the workers would take a couple of seconds. So it's recommended to disable it when testing your app. The rest of the code doesn't need to be changed. When Concurrent.js is disabled the load method would revert to the dynamic import function and other methods would do nothing.
//test/index.js
import concurrent from '@bitair/concurrent.js'
concurrent.config({
disabled: true
})
// ...rest of the code
You may also move this code to the app's main module and for example activate Concurrent.js only in production environment:
//index.js
import concurrent from '@bitair/concurrent.js'
concurrent.config({
disabled: process.env.NAME !== 'PROD'
})
// ...rest of the code
Parallelism
In order to achieve parallelism the load method accepts a flag named isolated
that tells Concurrent.js to allocate an individual worker to every instance of an object and also to every invocation of a function (here function indicates an exported function not a method of an instance). Concurrent.js has also a config method that can be used to set the number of max spawning threads. In parallelism, the number must be less than or equal to the total available CPU cores. In Node.js v19.4.0 or later the number can be extracted from os.availableParallelism:
import concurrent from '@bitair/concurrent.js'
concurrent.config({
maxThreads: 16 // Use os.availableParallelism() if your Node.js version is above v19.4.0.
})
const { Arithmetic } = await concurrent.load(new URL('./math.js', import.meta.url), {
isolated: true
})
const ops = []
for (let i = 0; i <= 20; i++) {
const arithmetic = new Arithmetic()
ops.push(
Promise.resolve(arithmetic.isPrime(i)).then(async result => {
await dispose(arithmetic) // The 17th operation will be waiting for a worker to be released, so removing this line would freeze the app.
return result
})
)
}
const results = await Promise.all(ops)
// ...rest of the code
API
IN PROGRESS
Notes
- All concurrent methods and functions must be called asynchronously.
- The data that would be sent to or received from concurrent instances/functions must be supported by the structured clone algorithm .
- Workers can't run TypeScript. So in case of using tools like
ts-node
the load method would perform a normal import using the dynamic import function. - All unhandled exceptions would be bubbled up to the main thread.
- Every function call and object instantiation would be run on an individual worker if available or share a worker if the isolated flag is not enabled.
- When using Concurrent.js on the client side the app and its concurrent modules should be bundled separately in order to not load the entire app inside a worker.
- Don't use Concurrent.js for IO-intensive tasks as the Node.js official document suggests: "Workers (threads) are useful for performing CPU-intensive JavaScript operations. They do not help much with I/O-intensive work. The Node.js built-in asynchronous I/O operations are more efficient than Workers can be."
Terminology
- Multitasking means running multiple processes side by side and ability to switch between them. It requires one thread.
- Concurrency means running multiple processes on multiple threads. It requires at least two threads. Concurrency is a performant form of multitasking.
- Parallelism means running multiple processes on multiple CPUs. It requires at least two CPUs. Parallelism is a performant form of concurrency.
Although this library is able to perform parallel computing when the host machine has multiple CPUs the name of the library isn't Parallel.js because it's impossible to back the name in the case of a host machine that has only one CPU.