Start · Sprachen · JavaScript · Referenz · MessageChannel

MessageChannel

Schnittstelle

Erzeugt einen bidirektionalen Nachrichtenkanal mit zwei verbundenen <code>MessagePort</code>-Objekten (<code>port1</code> und <code>port2</code>) für direkte Kommunikation zwischen Kontexten.

seit JavaScript Baseline: alle Browser (seit 2015); Node.js 15+ gl Kategorie: webapi

Signatur

interface MessageChannel

Beschreibung

MessageChannel gehört zur Channel Messaging API. Mit new MessageChannel() entsteht ein Paar fest verbundener Ports: Was über port1.postMessage() gesendet wird, kommt an port2 an, und umgekehrt. Ein Port bleibt meist im aktuellen Kontext. Den anderen überträgt man per postMessage() als Transferable an ein iframe, einen Worker oder ein anderes Fenster.

Dadurch entsteht ein privater, direkter Kanal. Anders als bei window.postMessage() muss man nicht jede eingehende Nachricht nach origin filtern und die Nachrichten nicht über ein globales message-Event verteilen. Typische Einsatzfälle sind:

  • Anfrage-Antwort-Protokolle zwischen Hauptseite und iframe oder Worker
  • die direkte Verbindung zweier Worker untereinander
  • Kommunikation mit einem Service Worker (Antwortkanal)
  • das Einplanen von Makrotasks, etwa in Schedulern wie dem von React

Die Daten werden mit dem Structured-Clone-Algorithmus kopiert. Objekte, Arrays, Map, Blob, ArrayBuffer und Ähnliches sind erlaubt, Funktionen und DOM-Knoten dagegen nicht. ArrayBuffer und weitere MessagePorts lassen sich auch ohne Kopie übertragen, indem man sie im Transfer-Array angibt.

Parameter

Name Typ Default Beschreibung
$(keine) — Der Konstruktor new MessageChannel() nimmt keine Argumente entgegen.
$port1 MessagePort Schreibgeschützte Eigenschaft: der erste Port des Kanals. Er ist mit port2 verbunden.
$port2 MessagePort Schreibgeschützte Eigenschaft: der zweite Port des Kanals. Meist wird er per postMessage(msg, [port2]) an einen anderen Kontext übertragen.

Rückgabewert

Typ
MessageChannel
Beschreibung
Ein neues MessageChannel-Objekt mit den verbundenen Ports port1 und port2.

Beispiele

Kanal erzeugen und lokal testen

const channel = new MessageChannel();

// Mit onmessage wird der Port automatisch gestartet
channel.port2.onmessage = (event) => {
  console.log('port2 empfängt:', event.data);
  channel.port2.postMessage({ ok: true });
};

channel.port1.onmessage = (event) => {
  console.log('port1 empfängt:', event.data);
};

channel.port1.postMessage({ text: 'Hallo' });
port2 empfängt: { text: 'Hallo' } port1 empfängt: { ok: true }

Privater Kanal zu einem iframe

// Hauptseite
const iframe = document.querySelector('iframe');
const { port1, port2 } = new MessageChannel();

port1.onmessage = ({ data }) => console.log('Antwort aus iframe:', data);

iframe.addEventListener('load', () => {
  // port2 wird übertragen (nicht kopiert); Zielursprung angeben!
  iframe.contentWindow.postMessage('init', 'https://widget.example.com', [port2]);
});

// Im iframe (https://widget.example.com):
// window.addEventListener('message', (event) => {
//   if (event.origin !== 'https://parent.example.com') return;
//   const port = event.ports[0];
//   port.postMessage('iframe bereit');
// });
Antwort aus iframe: iframe bereit

Anfrage-Antwort mit einem Worker per Promise

const worker = new Worker(URL.createObjectURL(new Blob([
  `self.onmessage = (e) => {
     const [port] = e.ports;
     port.postMessage(e.data.a + e.data.b);
   };`
], { type: 'text/javascript' })));

const request = (data) => new Promise((resolve) => {
  const { port1, port2 } = new MessageChannel();
  port1.onmessage = ({ data }) => {
    port1.close(); // Kanal nach der Antwort schließen
    resolve(data);
  };
  worker.postMessage(data, [port2]);
});

const summe = await request({ a: 2, b: 40 });
console.log('Ergebnis:', summe);
Ergebnis: 42

Makrotask einplanen (setTimeout(0) ohne Mindestverzögerung)

const { port1, port2 } = new MessageChannel();
const queue = [];
port1.onmessage = () => queue.shift()?.();

const scheduleTask = (fn) => {
  queue.push(fn);
  port2.postMessage(null);
};

scheduleTask(() => console.log('Makrotask'));
Promise.resolve().then(() => console.log('Mikrotask'));
console.log('synchron');
synchron Mikrotask Makrotask

// Wichtig · Fallstricke

  • Port starten: Wer addEventListener('message', …) statt onmessage verwendet, muss danach port.start() aufrufen. Sonst werden Nachrichten nur gepuffert und nie zugestellt.
  • Transfer statt Kopie: Ein übertragener Port ist im sendenden Kontext nicht mehr nutzbar. Man gibt ihn im Transfer-Array an (postMessage(msg, [port2])), sonst tritt ein DataCloneError auf.
  • Sicherheit: Beim ersten Übertragen per window.postMessage() sollte man immer einen konkreten Zielursprung angeben, nicht '*'. Beim Empfänger muss event.origin geprüft werden. Wer einen Port besitzt, kann uneingeschränkt senden. Eingehende Daten sind daher trotzdem zu prüfen und nie ungefiltert in innerHTML einzufügen (XSS-Gefahr).
  • Speicherlecks: Ports mit aktivem onmessage halten den Kanal am Leben. Nicht mehr benötigte Ports schließt man mit port.close().
  • Kein Fehlerkanal: Nachrichten, die sich nicht deserialisieren lassen, lösen am Empfänger das Event messageerror aus. Einen Bestätigungsmechanismus gibt es nicht, Zeitlimits muss man selbst bauen.
  • Kompatibilität: Die API wird von allen modernen Browsern unterstützt und ist auch in Web Workern verfügbar. In Node.js ist MessageChannel seit Version 15 global verfügbar, davor über require('worker_threads'). Die Node-Variante nutzt eine EventEmitter-ähnliche API (port.on('message')).