jskatas.org Continuously Learn JavaScript. Your Way.

Worker Threads in Nodejs vs. the Browser

I was surprised to see that the worker threads run in non-strict mode by default. Read more about how to control strict mode if you want to understand the other part of the problem we are about to tackle.

I always like to understand where things originate, so let's play with worker threads a bit.

The Problem to Solve

On my site jskatas I came to the point to use worker threads to run each test in an isolated environment.

I had started out with using new Function, later used new AsyncFunction (where AsyncFunction = async function () {}.constructor) to handle async functions properly. Once I got to running the let kata that created a global variable by just omitting the var keyword, the Function approach didn't work anymore. Everything runs in the browser's global scope, which made the variable exist and the next test "saw" it too and failed for that reason. So it was time to fully isolate the tests from each other.

Web Workers in the Browser

This is when I moved the tests to run in a worker thread. In the browser it was pretty easy, to create a Blob which contains the source code and pass it to the Worker. Almost too easy.

  const blob = new Blob([sourceCode], { type: 'application/javascript' });
  const worker = new Worker(URL.createObjectURL(blob));

The worker code became a bit more complex, in order to handle async, errors and pending promises properly. So I wanted to write tests for the worker code. But I failed due to the subtle but existing differences between worker threads in the browser and in nodejs.

Worker Threads in Nodejs

Worker threads in nodejs do not yet accept a Blob, as seen above in the browser code. So I tried passing the source code as a string using new Worker("data:text/javascript," + sourceCode, {eval: true}). I didn't get this to work. I ended up writing each worker file to disk and then passing the path to the worker, this works nicely but really slow.

Nodejs

  • we need to use require: const { parentPort } = require('worker_threads');
  • parentPort instead of self: const { parentPort } = require('worker_threads');
  • worker.cjs only runs in non-strict mode
  • every required file must be cjs