Signatur
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
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');
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!');
// });
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);
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)'));
// Wichtig · Fallstricke
- Port starten: Mit
port.onmessage = …wird der Port automatisch gestartet. BeiaddEventListener('message', …)muss man zusätzlichport.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 eineDataCloneError-DOMException. - Aufräumen: Nicht mehr benötigte Ports mit
port.close()schließen. Ein Port mit aktivemonmessage-Handler kann sonst den Garbage Collector daran hindern, ihn freizugeben, und in Node.js den Prozess am Beenden hindern (dort hilft auchport.unref()). - Sicherheit: Beim ersten
window.postMessage()zur Übergabe des Ports immer einen konkretentargetOriginstatt'*'angeben und auf der Empfängerseiteevent.originprüfen. Wer den Port hat, kann über ihn ohne weitere Origin-Prüfung Nachrichten schicken. Eingehende Daten deshalb nie ungeprüft ininnerHTMLo. Ä. 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. Ohnenewaufgerufen, wirft der Konstruktor einenTypeError.