unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
api-designbackwards-compatibility

worker: add ref/unref to web workers

Project: nodejs/node | Language: JavaScript | Concepts: api-design, backwards-compatibility | Difficulty: mid
Link: https://github.com/nodejs/node/pull/65507

The problem

Node.js Web Workers did not support the ability to control their event loop references using the familiar ref and unref methods, as is possible with worker_threads.Worker. This made it impossible to prevent a Web Worker from keeping the Node.js process alive, limiting parity and flexibility compared to other worker APIs in Node.js.

Why it's non-trivial

A naive fix might add .ref() and .unref() methods directly to the Web Worker class. However, Web Workers are a standard API, and adding non-standard methods to their prototype would break compatibility with the web platform and risk future conflicts. Node.js has an established protocol for this (process.ref()/process.unref() using well-known symbols), which must be followed to avoid polluting the global API surface.

The approach

1. Documenting the behavior and protocol

The documentation is updated to explain that Node.js Web Workers implement the [Refable protocol][] and can be ref'd and unref'd using process.ref(worker) and process.unref(worker):

+Web Workers, like `node:worker_threads` workers, keep the event loop alive by
+default. In Node.js, Web Workers implement the [Refable protocol][], and can be
+ref'd and unref'd using `process.ref(worker)` and `process.unref(worker)`.

This makes it clear to users that the Node.js-specific protocol should be used, not direct method calls.

2. Implementing the protocol using well-known symbols

Instead of adding .ref() and .unref() methods, the PR defines symbol-keyed methods on the Worker class:

+const kRef = SymbolFor('nodejs.ref');
+const kUnref = SymbolFor('nodejs.unref');
...
+  // The following properties are non-standard, Node.js extensions
+  [kRef]() {
+    validateThisInternalField(this, kWorker, 'Worker');
+    this[kWorker]?.ref();
+  }
+
+  [kUnref]() {
+    validateThisInternalField(this, kWorker, 'Worker');
+    this[kWorker]?.unref();
+  }
  • kRef and kUnref are well-known symbols used by the Node.js process API to look up reference management methods.
  • The methods delegate to the underlying Node.js worker implementation, preserving encapsulation and compatibility.

3. Testing the integration

A new test ensures that process.ref(worker) and process.unref(worker) work as expected, including for the case when the worker script fails to load:

const worker = new Worker(fixtures.fileURL('web-worker', 'nonexistent.js').href);
worker.addEventListener('error', common.mustCall());
process.unref(worker);
process.ref(worker);

And for a successful worker:

const worker = new Worker(fixtures.fileURL('web-worker', 'echo.js').href);
// ... test sending/receiving messages and ref/unref behavior
process.once('beforeExit', common.mustCall(() => {
  process.ref(worker);
  worker.postMessage('hello');
}));
process.unref(worker);

From the review thread

jasnell:
Since this is a standard API, adding non-standard ref/unref methods to it is a bit problematic. Please use the process.ref/process.unref protocol instead.

  • https://nodejs.org/docs/latest/api/process.html#processrefmayberefable
  • https://nodejs.org/docs/latest/api/process.html#processunrefmayberefable
const refSymbol = Symbol.for('nodejs.ref');
const unrefSymbol = Symbol.for('nodejs.unref');

class Foo {
  [refSymbol]() { /* replaces ref() */ }
  [unrefSymbol]() { /* replaces unref() */ }
}

const f = new Foo();
process.ref(f);
process.unref(f);

Renegade334:
Nit: pedantically, these methods are not "non-standard", since they belong to node:process, not the web API.

Web Workers, like `node:worker_threads` workers, keep the event loop alive by
default. In Node.js, Web Workers implement the [Refable protocol][], and can be
ref'd and unref'd using `process.ref(worker)` and `process.unref(worker)`.

[Refable protocol]: process.md#processrefmayberefable

Takeaways

  • When extending standard APIs, avoid adding non-standard methods directly; instead, use established extension mechanisms (such as symbol-keyed protocols).
  • Documentation should clearly communicate platform-specific extensions and how to use them, especially when they differ from web standards.
  • Tests should cover both success and failure scenarios, including edge cases like resource loading failures, to ensure robust integration.