Skip to content

Commit 9e446b3

Browse files
committedDec 1, 2020
worker: add experimental BroadcastChannel
Signed-off-by: James M Snell <[email protected]> PR-URL: nodejs#36271 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent 09fd8f1 commit 9e446b3

8 files changed

+689
-72
lines changed
 

‎doc/api/worker_threads.md

+92
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,98 @@ if (isMainThread) {
274274
}
275275
```
276276

277+
## Class: `BroadcastChannel extends EventTarget`
278+
<!-- YAML
279+
added: REPLACEME
280+
-->
281+
282+
> Stability: 1 - Experimental
283+
284+
Instances of `BroadcastChannel` allow asynchronous one-to-many communication
285+
with all other `BroadcastChannel` instances bound to the same channel name.
286+
287+
```js
288+
'use strict';
289+
290+
const {
291+
isMainThread,
292+
BroadcastChannel,
293+
Worker
294+
} = require('worker_threads');
295+
296+
const bc = new BroadcastChannel('hello');
297+
298+
if (isMainThread) {
299+
let c = 0;
300+
bc.onmessage = (event) => {
301+
console.log(event.data);
302+
if (++c === 10) bc.close();
303+
};
304+
for (let n = 0; n < 10; n++)
305+
new Worker(__filename);
306+
} else {
307+
bc.postMessage('hello from every worker');
308+
bc.close();
309+
}
310+
```
311+
312+
### `new BroadcastChannel(name)`
313+
<!-- YAML
314+
added: REPLACEME
315+
-->
316+
317+
* `name` {any} The name of the channel to connect to. Any JavaScript value
318+
that can be converted to a string using ``${name}`` is permitted.
319+
320+
### `broadcastChannel.close()`
321+
<!-- YAML
322+
added: REPLACEME
323+
-->
324+
325+
Closes the `BroadcastChannel` connection.
326+
327+
### `broadcastChannel.onmessage`
328+
<!-- YAML
329+
added: REPLACEME
330+
-->
331+
332+
* Type: {Function} Invoked with a single `MessageEvent` argument
333+
when a message is received.
334+
335+
### `broadcastChannel.onmessageerror`
336+
<!-- YAML
337+
added: REPLACEME
338+
-->
339+
340+
* Type: {Function} Invoked with a received message cannot be
341+
deserialized.
342+
343+
### `broadcastChannel.postMessage(message)`
344+
<!-- YAML
345+
added: REPLACEME
346+
-->
347+
348+
* `message` {any} Any cloneable JavaScript value.
349+
350+
### `broadcastChannel.ref()`
351+
<!-- YAML
352+
added: REPLACEME
353+
-->
354+
355+
Opposite of `unref()`. Calling `ref()` on a previously `unref()`ed
356+
BroadcastChannel will *not* let the program exit if it's the only active handle
357+
left (the default behavior). If the port is `ref()`ed, calling `ref()` again
358+
will have no effect.
359+
360+
### `broadcastChannel.unref()`
361+
<!-- YAML
362+
added: REPLACEME
363+
-->
364+
365+
Calling `unref()` on a BroadcastChannel will allow the thread to exit if this
366+
is the only active handle in the event system. If the BroadcastChannel is
367+
already `unref()`ed calling `unref()` again will have no effect.
368+
277369
## Class: `MessageChannel`
278370
<!-- YAML
279371
added: v10.5.0

‎lib/internal/worker/io.js

+83-4
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ const {
1919
const {
2020
MessagePort,
2121
MessageChannel,
22+
broadcastChannel,
2223
drainMessagePort,
2324
moveMessagePortToContext,
2425
receiveMessageOnPort: receiveMessageOnPort_,
2526
stopMessagePort,
26-
checkMessagePort
27+
checkMessagePort,
28+
DOMException,
2729
} = internalBinding('messaging');
2830
const {
2931
getEnvMessagePort
@@ -41,14 +43,20 @@ const {
4143
} = require('internal/event_target');
4244
const { inspect } = require('internal/util/inspect');
4345
const {
44-
ERR_INVALID_ARG_TYPE
45-
} = require('internal/errors').codes;
46+
codes: {
47+
ERR_INVALID_ARG_TYPE,
48+
ERR_MISSING_ARGS,
49+
}
50+
} = require('internal/errors');
4651

4752
const kData = Symbol('kData');
53+
const kHandle = Symbol('kHandle');
4854
const kIncrementsPortRef = Symbol('kIncrementsPortRef');
4955
const kLastEventId = Symbol('kLastEventId');
5056
const kName = Symbol('kName');
5157
const kOrigin = Symbol('kOrigin');
58+
const kOnMessage = Symbol('kOnMessage');
59+
const kOnMessageError = Symbol('kOnMessageError');
5260
const kPort = Symbol('kPort');
5361
const kPorts = Symbol('kPorts');
5462
const kWaitingStreams = Symbol('kWaitingStreams');
@@ -324,6 +332,76 @@ function receiveMessageOnPort(port) {
324332
return { message };
325333
}
326334

335+
function onMessageEvent(type, data) {
336+
this.dispatchEvent(new MessageEvent(type, { data }));
337+
}
338+
339+
class BroadcastChannel extends EventTarget {
340+
constructor(name) {
341+
if (arguments.length === 0)
342+
throw new ERR_MISSING_ARGS('name');
343+
super();
344+
this[kName] = `${name}`;
345+
this[kHandle] = broadcastChannel(this[kName]);
346+
this[kOnMessage] = onMessageEvent.bind(this, 'message');
347+
this[kOnMessageError] = onMessageEvent.bind(this, 'messageerror');
348+
this[kHandle].on('message', this[kOnMessage]);
349+
this[kHandle].on('messageerror', this[kOnMessageError]);
350+
}
351+
352+
[inspect.custom](depth, options) {
353+
if (depth < 0)
354+
return 'BroadcastChannel';
355+
356+
const opts = {
357+
...options,
358+
depth: options.depth == null ? null : options.depth - 1
359+
};
360+
361+
return `BroadcastChannel ${inspect({
362+
name: this[kName],
363+
active: this[kHandle] !== undefined,
364+
}, opts)}`;
365+
}
366+
367+
get name() { return this[kName]; }
368+
369+
close() {
370+
if (this[kHandle] === undefined)
371+
return;
372+
this[kHandle].off('message', this[kOnMessage]);
373+
this[kHandle].off('messageerror', this[kOnMessageError]);
374+
this[kOnMessage] = undefined;
375+
this[kOnMessageError] = undefined;
376+
this[kHandle].close();
377+
this[kHandle] = undefined;
378+
}
379+
380+
postMessage(message) {
381+
if (arguments.length === 0)
382+
throw new ERR_MISSING_ARGS('message');
383+
if (this[kHandle] === undefined)
384+
throw new DOMException('BroadcastChannel is closed.');
385+
if (this[kHandle].postMessage(message) === undefined)
386+
throw new DOMException('Message could not be posted.');
387+
}
388+
389+
ref() {
390+
if (this[kHandle])
391+
this[kHandle].ref();
392+
return this;
393+
}
394+
395+
unref() {
396+
if (this[kHandle])
397+
this[kHandle].unref();
398+
return this;
399+
}
400+
}
401+
402+
defineEventHandler(BroadcastChannel.prototype, 'message');
403+
defineEventHandler(BroadcastChannel.prototype, 'messageerror');
404+
327405
module.exports = {
328406
drainMessagePort,
329407
messageTypes,
@@ -339,5 +417,6 @@ module.exports = {
339417
setupPortReferencing,
340418
ReadableWorkerStdio,
341419
WritableWorkerStdio,
342-
createWorkerStdio
420+
createWorkerStdio,
421+
BroadcastChannel,
343422
};

‎lib/worker_threads.js

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
MessageChannel,
1414
moveMessagePortToContext,
1515
receiveMessageOnPort,
16+
BroadcastChannel,
1617
} = require('internal/worker/io');
1718

1819
const {
@@ -32,4 +33,5 @@ module.exports = {
3233
Worker,
3334
parentPort: null,
3435
workerData: null,
36+
BroadcastChannel,
3537
};

0 commit comments

Comments
 (0)
Please sign in to comment.