jsrosetta

Async

Message Passing

How Node.js's MessageChannel compares to channels (Go), mpsc (Rust), AsyncStream (Swift), and BlockingQueue (Java) for sending data between tasks.

Minimum versions
Node.js ≥ 12.20Go ≥ 1.0Rust ≥ 1.58Swift ≥ 5.7Java ≥ 25
Verified on
Node.js 24.12.0Go 1.27.1Rust 1.98.1Swift 6.2.4Java 25.0.4.1

Node.js code is an ES module: save it as .mjs or set "type": "module" in package.json.

Instead of letting two concurrent tasks read and write the same shared variable, all five languages let you send data back and forth between them. Node.js does this through the MessageChannel/postMessage API. Go has channels (chan) built into the language itself, with dedicated syntax (<-, select) for sending, receiving, closing, and waiting with a timeout. Rust has nothing at the language level for this — std::sync::mpsc is just an ordinary standard library type, used through regular method calls (.send(), .recv(), .recv_timeout()) with no special syntax at all. Swift has no channel type; AsyncStream is the closest thing — an async queue that one side yield()s into and the other reads with for await, but it doesn't block when full the way a real channel does. Java has BlockingQueue (SynchronousQueue, ArrayBlockingQueue, …) — the closest match to Go's channel among the remaining three languages.

Sending data between two tasks (MessageChannel vs channel)

import { MessageChannel } from "node:worker_threads";
 
const { port1, port2 } = new MessageChannel();
 
port2.on("message", (msg) => {
  console.log("port2 received:", msg); // → port2 received: hello from port1
  port1.close();
  port2.close();
});
 
port1.postMessage("hello from port1");

Key differences

Node.js Go Rust Swift Java
Mechanism MessageChannel/postMessage channel (chan) — built into the language's syntax std::sync::mpsc — an ordinary standard library type, no special syntax AsyncStream — never blocks on send BlockingQueue (SynchronousQueue, …)
Capacity limit unbounded, an internal hidden queue a fixed buffer set at creation (make(chan T, n)) sync_channel(n) for a fixed buffer no real backpressure ArrayBlockingQueue(n) for a fixed buffer
Waiting with a timeout hand-rolled with Promise.race + setTimeout select + time.After built into the syntax recv_timeout(Duration) racing with TaskGroup + Task.sleep poll(timeout, unit)

Reference: github.com/miguelmota/golang-for-nodejs-developers#message-passing