Signatur
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
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' });
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');
// });
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);
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');
// Wichtig · Fallstricke
- Port starten: Wer
addEventListener('message', …)stattonmessageverwendet, muss danachport.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 einDataCloneErrorauf. - Sicherheit: Beim ersten Übertragen per
window.postMessage()sollte man immer einen konkreten Zielursprung angeben, nicht'*'. Beim Empfänger mussevent.origingeprüft werden. Wer einen Port besitzt, kann uneingeschränkt senden. Eingehende Daten sind daher trotzdem zu prüfen und nie ungefiltert ininnerHTMLeinzufügen (XSS-Gefahr). - Speicherlecks: Ports mit aktivem
onmessagehalten den Kanal am Leben. Nicht mehr benötigte Ports schließt man mitport.close(). - Kein Fehlerkanal: Nachrichten, die sich nicht deserialisieren lassen, lösen am Empfänger das Event
messageerroraus. 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
MessageChannelseit Version 15 global verfügbar, davor überrequire('worker_threads'). Die Node-Variante nutzt eine EventEmitter-ähnliche API (port.on('message')).