diff --git a/doc/api/globals.md b/doc/api/globals.md index cd6afed9626f..8a5375bcfe8c 100644 --- a/doc/api/globals.md +++ b/doc/api/globals.md @@ -1401,6 +1401,10 @@ Inside a worker, \[`worker_threads.parentPort`]\[] is the port behind `self.postMessage()` and the worker's `message` events, `isMainThread` is `false`, and `workerData` is `undefined`. +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)`. + As a rule of thumb, use [`node:worker_threads`][] directly when a program needs `workerData`, a custom `env` or `execArgv`, resource limits, stdio redirection, the `'online'` and `'exit'` events, or `worker.threadId`; @@ -1457,6 +1461,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][]. [HTML Standard]: https://html.spec.whatwg.org/multipage/workers.html [Navigator API]: https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object [RFC 5646]: https://www.rfc-editor.org/rfc/rfc5646.txt +[Refable protocol]: process.md#processrefmayberefable [Web Crypto API]: webcrypto.md [`--experimental-eventsource`]: cli.md#--experimental-eventsource [`--experimental-web-worker`]: cli.md#--experimental-web-worker diff --git a/lib/internal/webworker.js b/lib/internal/webworker.js index bc7b55ed8ac1..e2a2086f9039 100644 --- a/lib/internal/webworker.js +++ b/lib/internal/webworker.js @@ -99,6 +99,8 @@ const { const kCurrentlyReceivingPorts = SymbolFor('nodejs.internal.kCurrentlyReceivingPorts'); +const kRef = SymbolFor('nodejs.ref'); +const kUnref = SymbolFor('nodejs.unref'); const kCreate = Symbol('kCreate'); const kInsidePort = Symbol('kInsidePort'); @@ -865,6 +867,17 @@ class Worker extends EventTarget { // arguments, and returned the same return value." this[kWorker]?.postMessage(message, transfer); } + + // 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(); + } } ObjectDefineProperties(Worker.prototype, { diff --git a/test/fixtures/web-worker/echo.js b/test/fixtures/web-worker/echo.js new file mode 100644 index 000000000000..753c32599775 --- /dev/null +++ b/test/fixtures/web-worker/echo.js @@ -0,0 +1,5 @@ +'use strict'; + +addEventListener('message', (event) => { + postMessage(event.data); +}); diff --git a/test/parallel/test-webworker-ref-unref.js b/test/parallel/test-webworker-ref-unref.js new file mode 100644 index 000000000000..cff2bb5d097f --- /dev/null +++ b/test/parallel/test-webworker-ref-unref.js @@ -0,0 +1,29 @@ +// Flags: --experimental-web-worker +'use strict'; + +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); + +{ + // Both are no-ops on a worker whose script could not be loaded. + const worker = new Worker(fixtures.fileURL('web-worker', 'nonexistent.js').href); + worker.addEventListener('error', common.mustCall()); + process.unref(worker); + process.ref(worker); +} + +const worker = new Worker(fixtures.fileURL('web-worker', 'echo.js').href); + +worker.addEventListener('error', common.mustNotCall('worker failed')); +worker.addEventListener('message', common.mustCall(({ data }) => { + assert.strictEqual(data, 'hello'); + worker.terminate(); +})); + +process.once('beforeExit', common.mustCall(() => { + process.ref(worker); + worker.postMessage('hello'); +})); + +process.unref(worker);