Start · Sprachen · JavaScript · Referenz · new MessageChannel()

new MessageChannel()

Methode

Erzeugt ein neues <code>MessageChannel</code>-Objekt mit zwei verbundenen <code>MessagePort</code>-Objekten (<code>port1</code> und <code>port2</code>) für die direkte bidirektionale Kommunikation.

seit JavaScript Baseline: alle Browser (seit 2015), Web Worker, No Kategorie: webapi

Signatur

new MessageChannel()

Beschreibung

Der Konstruktor new MessageChannel() legt einen neuen Nachrichtenkanal an. Er nimmt keine Argumente entgegen und liefert ein Objekt mit den schreibgeschützten Eigenschaften port1 und port2. Was über den einen Port per postMessage() geschickt wird, kommt am anderen Port als message-Event an – und umgekehrt.

Typischerweise behält man port1 im aktuellen Kontext und überträgt port2 an einen anderen Kontext, etwa ein <iframe>, einen Web Worker, einen Service Worker oder ein anderes Fenster. Dafür gibt man den Port im Transfer-Array von postMessage(message, targetOrigin, [port2]) bzw. worker.postMessage(message, [port2]) an. Ab dann gibt es einen privaten, direkten Kanal zwischen beiden Seiten, der nicht mehr über das globale message-Event des Fensters läuft.

Das ist praktisch für Anfrage-Antwort-Muster (jede Anfrage bekommt ihren eigenen Kanal, sodass die Antwort eindeutig zugeordnet werden kann), für die Trennung mehrerer Kommunikationsstränge und für die direkte Verbindung zweier Worker untereinander. Gelegentlich wird ein MessageChannel auch als Makrotask-Scheduler genutzt, weil Nachrichten ohne die Mindestverzögerung von setTimeout zugestellt werden.

Rückgabewert

Typ
MessageChannel
Beschreibung
Ein neues MessageChannel-Objekt mit den verbundenen Ports port1 und port2 (jeweils vom Typ MessagePort).

Beispiele

Grundlagen: Kanal erzeugen und Nachrichten austauschen

const channel = new MessageChannel();
const { port1, port2 } = channel;

// onmessage startet den Port automatisch
port2.onmessage = (event) => {
  console.log('port2 empfängt:', event.data);
  port2.postMessage({ antwort: 'pong' });
};

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

port1.postMessage('ping');
port2 empfängt: ping port1 empfängt: { antwort: 'pong' }

Privater Kanal zu einem iframe

// Elternseite
const iframe = document.querySelector('#widget');

iframe.addEventListener('load', () => {
  const channel = new MessageChannel();

  channel.port1.onmessage = (event) => {
    console.log('Vom iframe:', event.data);
  };

  // port2 wird übertragen – danach ist er hier nicht mehr nutzbar
  iframe.contentWindow.postMessage('init', 'https://widget.example.com', [channel.port2]);
});

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

Anfrage-Antwort mit einem Worker als Promise

const worker = new Worker('rechner.js');

const anfrage = (daten) => new Promise((resolve, reject) => {
  const { port1, port2 } = new MessageChannel();
  port1.onmessage = ({ data }) => {
    port1.close();
    data.error ? reject(new Error(data.error)) : resolve(data.result);
  };
  worker.postMessage(daten, [port2]);
});

// rechner.js:
// self.onmessage = ({ data, ports }) => {
//   ports[0].postMessage({ result: data.a + data.b });
// };

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

Schneller Makrotask-Scheduler

const { port1, port2 } = new MessageChannel();
const queue = [];

port1.onmessage = () => queue.shift()?.();

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

console.log('A');
planeTask(() => console.log('C (Makrotask)'));
Promise.resolve().then(() => console.log('B (Mikrotask)'));
A B (Mikrotask) C (Makrotask)

// Wichtig · Fallstricke

  • Port starten: Mit port.onmessage = … wird der Port automatisch gestartet. Bei addEventListener('message', …) muss man zusätzlich port.start() aufrufen, sonst werden Nachrichten gepuffert und nie zugestellt.
  • Übertragung statt Kopie: Ein Port muss im Transfer-Array von postMessage() stehen. Danach ist er im Ursprungskontext unbrauchbar. Gibt man ihn nur als Teil der Nachricht ohne Transfer-Liste mit, wirft der Aufruf eine DataCloneError-DOMException.
  • Aufräumen: Nicht mehr benötigte Ports mit port.close() schließen. Ein Port mit aktivem onmessage-Handler kann sonst den Garbage Collector daran hindern, ihn freizugeben, und in Node.js den Prozess am Beenden hindern (dort hilft auch port.unref()).
  • Sicherheit: Beim ersten window.postMessage() zur Übergabe des Ports immer einen konkreten targetOrigin statt '*' angeben und auf der Empfängerseite event.origin prüfen. Wer den Port hat, kann über ihn ohne weitere Origin-Prüfung Nachrichten schicken. Eingehende Daten deshalb nie ungeprüft in innerHTML o. Ä. schreiben.
  • Daten: Nachrichten werden per Structured-Clone-Algorithmus kopiert. Funktionen, DOM-Knoten und Klasseninstanzen samt Prototyp lassen sich nicht übertragen.
  • Kompatibilität: Wird von allen modernen Browsern unterstützt, auch in Web Workern. In Node.js ist die Klasse ab Version 15 global verfügbar, davor über require('worker_threads').MessageChannel. Ohne new aufgerufen, wirft der Konstruktor einen TypeError.