concurrent.js

Non-blocking Computation for JavaScript RTEs (Web Browsers, Node.js & Deno)

README

concurrent.js


At the highest level of its design, Concurrent.js is a dynamic module importer like require and import. But instead of loading a module into the main thread, it loads the module into a background thread. Concurrent.js helps with non-blocking computation on JavaScript RTEs and also facilitates interoperability with other languages in order to achieve better performance and richer computational libraries.

Sponsors


If you are interested in sponsoring the project, please contact us at hello@bitair.org.

Features


- [x] Platform support
  - [x] Web browsers
  - [x] Node.js
  - [x] Deno
- [ ] Language support
  - [x] JavaScript (ECMAScript & CommonJS)
  - [x] WebAssembly
  - [ ] TypeScript (Server-side)
  - [ ] C (Server-side)
  - [ ] Rust (Server-side)
  - [ ] Python (Server-side)
- [x] Parallel execution
- [ ] Reactive concurrency
- [ ] Inter-worker data sharing
- [ ] Multithreaded dependency resolver
- [ ] Sandboxing

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:

  1. ```bash
  2. bash hello_world.sh
  3. ```

Usage


Running JavaScript


  1. ```js
  2. import { concurrent } from '@bitair/concurrent.js'

  3. // import and load a JS module into a worker
  4. const { SampleObject, sampleFunction } = await concurrent.import('sample-js-module.js').load()

  5. // run a function
  6. const result = await sampleFunction(/*...args*/)

  7. // run a class (instance members)
  8. const obj = await new SampleObject(/*...args*/) // instantiate
  9. const value = await obj.sampleProp // get a field or getter
  10. await ((obj.sampleProp = 1), obj.sampleProp) // set a field or setter
  11. const result = await obj.sampleMethod(/*...args*/) // call a method

  12. // run a class (static members)
  13. const value = await SampleObject.sampleStaticProp // get a static field or getter
  14. await ((SampleObject.sampleStaticProp = 1), SampleObject.sampleStaticProp) // set a static field or setter
  15. const result = await SampleObject.sampleStaticMethod(/*...args*/) // call a static method

  16. // terminate Concurrent.js
  17. await concurrent.terminate()
  18. ```

Running WebAssembly


  1. ```js
  2. import { concurrent } from '@bitair/concurrent.js'

  3. // import and load a wasm module into a worker
  4. const { sampleFunction } = await concurrent.import('sample-wasm-module.wasm').load()

  5. // run a function
  6. const result = await sampleFunction(/*...args*/)

  7. // terminate Concurrent.js
  8. await concurrent.terminate()
  9. ```

Projects

If you have built a project that uses Concurrent.js and you want to list it here, please email its details to hello@bitair.org.

- Browser
  - Basic usage (Sample)

- Node
  - Basic usage (Sample)

- Deno
  - Basic usage (Sample)

Sample

Node.js (ECMAScript)


  1. ```bash
  2. npm i @bitair/concurrent.js@latest
  3. ```

index.js

  1. ```js
  2. import { concurrent } from '@bitair/concurrent.js'
  3. const { factorial } = await concurrent.import('extra-bigint').load()
  4. const result = await factorial(50n)
  5. console.log(result)
  6. await concurrent.terminate()
  7. ```

package.json

  1. ```json
  2. {
  3.   "type": "module",
  4.   "dependencies": {
  5.     "@bitair/concurrent.js": "^0.6.1",
  6.     "extra-bigint": "^1.1.10"
  7.   }
  8. }
  9. ```

  1. ```bash
  2. node .
  3. ```

Deno


index.ts

  1. ```js
  2. import { concurrent } from 'https://deno.land/x/concurrentjs@v0.6.1/mod.ts'
  3. const { factorial } = await concurrent.import(new URL('services/index.ts', import.meta.url)).load()
  4. const result = await factorial(50n)
  5. console.log(result)
  6. await concurrent.terminate()
  7. ```

services/index.ts

  1. ```js
  2. export { factorial } from 'extra-bigint'
  3. ```

deno.json

  1. ```json
  2. {
  3.   "imports": {
  4.     "extra-bigint": "npm:extra-bigint@^1.1.10"
  5.   }
  6. }
  7. ```

  1. ```bash
  2. deno run --allow-read --allow-net index.ts
  3. ```

Browser


  1. ```
  2. .
  3. src
  4.      services
  5.          index.js
  6.      app.js
  7.      worker_script.js
  8.     .
  9. static
  10.      index.html
  11. .
  12. ```

app.js

  1. ```js
  2. import { concurrent } from '@bitair/concurrent.js'
  3. const { factorial } = await concurrent.import(new URL('services/index.js', import.meta.url)).load()
  4. const result = await factorial(50n)
  5. console.log(result)
  6. await concurrent.terminate()
  7. ```

services/index.js

  1. ```js
  2. export { factorial } from 'extra-bigint'
  3. ```

worker_script.js

  1. ```js
  2. import '@bitair/concurrent.js/worker_script'
  3. ```

index.html

  1. ```html
  2. <!DOCTYPE html>
  3. <html>
  4.   <body>
  5.     <script type="module" src="scripts/main.js"></script>
  6.   </body>
  7. </html>
  8. ```

build.sh

  1. ```bash
  2. #!/bin/bash
  3. npx esbuild src/app.js --bundle --format=esm --platform=browser --target=esnext --outfile=static/scripts/main.js
  4. npx esbuild src/worker_script.js --bundle --format=esm --platform=browser --target=esnext --outfile=static/scripts/worker_script.js
  5. npx esbuild src/services/index.js --bundle --format=esm --platform=browser --target=esnext --outfile=static/scripts/services/index.js
  6. ```

package.json

  1. ```json
  2. {
  3.   "type": "module",
  4.   "dependencies": {
  5.     "@bitair/concurrent.js": "^0.6.1",
  6.     "http-server": "^14.1.1",
  7.     "extra-bigint": "^1.1.10"
  8.   },
  9.   "devDependencies": {
  10.     "esbuild": "^0.17.8"
  11.   }
  12. }
  13. ```

  1. ```bash
  2. bash ./build.sh && npx http-server static
  3. ```

Parallelism


  1. ```js
  2. import { concurrent } from '@bitair/concurrent.js'

  3. const extraBigint = concurrent.import('extra-bigint')

  4. concurrent.config({ maxThreads: 16 }) // Instead of a hardcoded value use os.availableParallelism() in Node.js v19.4.0 or later

  5. const ops = []
  6. for (let i = 0; i <= 100; i++) {
  7.   const { factorial } = await extraBigint.load()
  8.   ops.push(factorial(i))
  9. }

  10. const results = await Promise.all(ops)
  11. // ...rest of the code

  12. await concurrent.terminate()
  13. ```

API


  1. ```ts
  2. concurrent.import<T>(src: URL | string): IConcurrentModule<T>
  3. ```

Imports and prepares the module for being loaded into workers. Note that only functions and classes can be imported. Importing and accessing classes only works on JavaScript and TypeScript languages.

- src: URL | string

  Source of the module. Must be either a URL or a package name. Note that passing a package name is only applicable in Node.js.

  1. ```ts
  2. IConcurrentModule<T>.load() : Promise<T>
  3. ```

Loads the module into a worker.

  1. ```ts
  2. concurrent.config(settings: ConcurrencySettings): void
  3. ```

Configs the global settings of Concurrent.js.

- settings: ConcurrencySettings

  - 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.

  1. ```ts
  2. concurrent.terminate(force?: boolean): Promise<void>
  3. ```

Terminates Concurrent.js.

- force?: boolean [Not implemented]

  Forces Concurrent.js to exit immediately without waiting for workers to finish their tasks.

License