- Notifications
You must be signed in to change notification settings - Fork0
A JS communication port that can pass messages synchronously across workers
License
sass/sync-message-port
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This package exposes a utility class that encapsulates the ability to send andreceive messages with arbitrary structure across Node.js worker boundaries. Itcan be used as the building block for synchronous versions of APIs that aretraditionally only available asynchronously in the Node.js ecosystem by runningthe asynchronous APIs in a worker and accessing their results synchronously fromthe main thread.
Seethesync-child-process
package for an example ofsync-message-port
inaction.
Use
SyncMessagePort.createChannel()
to create a message channel that'sset up to be compatible withSyncMessagePort
s. A normalMessageChannel
won't work!You can send this
MessageChannel
's ports across worker boundaries just likeany otherMessagePort
. Send one to the worker you want to communicate withsynchronously.Once you're ready to start sending and receiving messages, wrapboth portsin
new SyncMessagePort()
, even if one is only ever going to be sendingmessages and not receiving them.Use
SyncMessagePort.postMessage()
to send messages andSyncMessagePort.receiveMessage()
to receive them synchronously.
import{Worker}from'node:worker_threads';import{SyncMessagePort}from'sync-message-port';// or// const {SyncMessagePort} = require('sync-message-port');// Channels must be created using this function. A MessageChannel created by// hand won't work.constchannel=SyncMessagePort.createChannel();constlocalPort=newSyncMessagePort(channel.port1);constworker=newWorker(` import {workerData} from 'node:worker_threads'; import {SyncMessagePort} from 'sync-message-port'; const remotePort = new SyncMessagePort(workerData.port); setTimeout(() => { remotePort.postMessage("hello from worker!"); }, 2000);`,{workerData:{port:channel.port2},transferList:[channel.port2],eval:true,});// Note that because workers report errors asynchronously, this won't report an// error if the worker fails to load because the main thread will be// synchronously waiting for its first message.worker.on('error',console.error);console.log(localPort.receiveMessage());
Although JavaScript in general and Node.js in particular are typically designedto embrace asynchrony, there are a number of reasons why a synchronous API maybe preferable or even necessary.
Althoughasync
/await
and thePromise
API has substantially improved theusability of writing asynchronous code in JavaScript, it doesn't address onecore issue: there's no way to write code that'spolymorphic over asynchrony.Put in simpler terms, there's no language-level way to write a complex functionthat takes a callback and to run that functions synchronously if the callback issynchronous and asynchronously otherwise. The only option is to write thefunction twice.
This poses a real, practical problem when interacting with libraries. Supposeyou have a library that takes a callback option—for example, an HTMLsanitization library that takes a callback to determine how to handle a given<a href="...">
. The library doesn't need to do any IO itself, so it's writtensynchronously. But what if your callback wants to make an HTTP request todetermine how to handle a tag? You're stuck unless you can make that requestsynchronous. This library makes that possible.
Asynchrony is generally more performant in situations where there's a largeamount of concurrent IO happening. But when performance is CPU-bound, it's oftensubstantially worse due to the overhead of bouncing back and forth between theevent loop and user code.
As a real-world example, the Sass compiler API supports both synchronous andasynchronous code paths to work around the polymorphism problem described above.The logic of these paths is exactly the same—the only difference is that theasynchronous path's functions all returnPromise
s instead of synchronousvalues. Compiling with the asynchronous path often takes 2-3x longer than withthe synchronous path. This means that being able to run plugins synchronouslycan provide a substantial overall performance gain, even if the pluginsthemselves lose the benefit of concurrency.
This usesAtomics
andSharedArrayBuffer
under the covers to signalacross threads when messages are available, andworker_threads.receiveMessageOnPort()
to actually retrieve messages.
Unfortunately, no. Browsers don't support any equivalent ofworker_threads.receiveMessageOnPort()
, even within worker threads. You couldmake a similar package that can transmit only binary data (or data that can beencoded as binary) using onlySharedArrayBuffer
, but that's outside the scopeof this package.
Disclaimer: this is not an official Google product.
About
A JS communication port that can pass messages synchronously across workers