JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 37
  • Score
    100M100P100Q59431F
  • License MIT

Concurrent Computing for JavaScript

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. Concurrent.js is designed to load JavaScript modules into workers and access them as usual. It provides a "write less, do more" isomorphic API and works on Node.js, Deno, and web browsers.

Features

  • Loading CommonJS modules into workers
  • Loading ECMAScript modules
  • Accessing exported classes
    • Instantiation
    • Instance members
      • Methods
      • Getters and setters
      • Fields
    • Static members
      • Methods
      • Getters and setters
      • Fields
  • Accessing exported functions
  • 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 calculator module with a prime number checker:

// calculator.js
export class Calculator {
  isPrime(n) {
    const j = Math.sqrt(n)
    for (let i = 2; i <= j; i++) {
      if (n % i === 0) return false
    }
    return n > 1
  }
}

In order to run it without blocking the main thread:

  1. Instead of using require or import functions use the load method with the module's absolute path or URL:

    const { Calculator } = await concurrent.load(CALCULATOR_MODULE_SRC)
  2. Instantiate an object of an exported class/constructor in the usual way:

    const calculator = new Calculator()
  3. Invoke any method of the instance. This should be done asynchronously (e.g. using the await operator):

    const result = await calculator.isPrime(123456789)
  4. Explicitly delete the instance before leaving its scope (this will be automated by implementing a garbage collector in later versions):

    await concurrent.dispose(calculator)
  5. Terminate Concurrent.js before exiting the app:

    await concurrent.terminate()

Please see the sample projects for a complete code. In order to run a sample project:

cd SAMPLE_DIR
npm i
npm build
npm start

Client-side usage

A client-side app should consist of three scripts/bundles:

  1. One that contains the app's code
  2. Another one that contains the concurrent modules' code
  3. A worker_script.js bundle that must be generated from the following code:
    import '@bitair/concurrent.js/worker_script'

Parallelism

In order to achieve parallelism, the number of total workers must be less than or equal to the total available CPUs/cores. In Node.js v19.4.0 or later the number can be extracted from os.availableParallelism.

The load method has a flag named isolated that tells Concurrent.js to allocate an individual worker to every instance of an object and to every invocation of a function (function indicates an exported function not a method of an instance):

import concurrent from '@bitair/concurrent.js'

concurrent.config({
  maxThreads: 16
})

const { Calculator } = await concurrent.load(
  new URL('./sample_services/index.js', import.meta.url),
  {
    isolated: true
  }
)

const ops = []
for (let i = 0; i <= 20; i++) {
  const calculator = new Calculator()
  ops.push(
    Promise.resolve(calculator.isPrime(i)).then(async result => {
      // The 17th operation will be waiting for a worker to be released.
      // So removing the next line will freeze the app
      await dispose(calculator)
      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.

License

MIT License