Web Workers

Workers are the way to run javascript off of the main thread. This can be useful to run compute heavy code without blocking the main thread.

Workers are a statdard, supported by browsers and also server side runtimes like Deno. In node.js, there is the worker_thread package.

An Example

Add a worker.ts:

function findPrime(max) {
  for (let i = max; i >= 2; i--) { 
    let isPrime = true
    for (let j = 2; j * j <= i; j ++) { 
      if (i % j === 0) {
        isPrime = false
        break 
      }
    }
    if (isPrime) return i
  }
}

self.onmessage = function(event) {
  self.postMessage(findPrime(event.data))
}

Here is how you can invoke it from main thread:

const worker = new Worker(
  new URL("./worker.ts", import.meta.url).href,
  {type: "module"}
)

worker.postMessage(1_000_000_000_000)

self.onmessage = function(event) {
  console.log(event.data)
}
$ deno --allow-read main.ts                                                      <<<
999999999989

A Callable Worker

In the previous example, it's hard to get the result of the computation, here is a solution:

class CallableWorker {
  private worker: Worker
  private id = 0
  private calls = new Map()

  constructor(url: string) {
    this.worker = new Worker(url, {type: "module"})
    this.worker.onmessage = (event) => {
      const [id, err, result] = event.data
      const [resolve, reject] = this.calls.get(id)
      err ? reject(err) : resolve(result)
      this.calls.delete(id)
    }
  }

  call(method: string, args: any[]) {
    this.id ++
    const id = this.id
    this.worker.postMessage([id, method, args]);
    return new Promise((resolve, reject) => {
      this.calls.set(id, [resolve, reject])
    })
  }
}

const worker = new CallableWorker(
  new URL("./worker.ts", import.meta.url).href
)

console.log(await worker.call("findPrime", [1_000_000_000_000]))

In worker.ts:

function findPrime(max) {
  for (let i = max; i >= 2; i--) { 
    let isPrime = true
    for (let j = 2; j * j <= i; j ++) { 
      if (i % j === 0) {
        isPrime = false
        break 
      }
    }
    if (isPrime) return i
  }
}

const methods = {
  findPrime
}

self.onmessage = async function(event) {
  const [id, method, args] = event.data
  if (!methods[method]) {
    self.postMessage([id, new Error(`Method not defined: ${method}`)])
    return
  }
  let result, err
  try {
    result = await methods[method](...args)
  } catch (e) {
    err = e
  }
  self.postMessage([id, err, result])
}