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();
+ }
kRefandkUnrefare 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.