Signatur
Beschreibung
Ein MessageChannel besteht aus zwei miteinander verbundenen MessagePort-Objekten: port1 und port2. Was an einem Port mit postMessage() gesendet wird, kommt am jeweils anderen Port als message-Event an. port1 ist per Konvention der Port, den der Kontext, der den Kanal erzeugt hat, behält. port2 wird dagegen an einen anderen Kontext übertragen, etwa an ein iframe, einen Worker oder einen Service Worker.
Technisch sind beide Ports gleichwertig. Die Zuordnung „port1 bleibt hier, port2 geht weg“ ist nur eine verbreitete Konvention. Ihr könnt auch port1 übertragen und port2 behalten.
Typische Anwendungsfälle sind eine private, direkte Verbindung zwischen zwei Kontexten ohne globales window.postMessage, Anfrage-Antwort-Muster mit Service Workern und das Planen von Aufgaben als Makrotask (z. B. in React-Schedulern).
Die Eigenschaft ist schreibgeschützt. Eine Zuweisung wird ignoriert, im Strict Mode wirft sie einen TypeError. Jeder Zugriff liefert dasselbe MessagePort-Objekt.
Rückgabewert
port2 verbunden und hat die Methoden postMessage(), start() und close() sowie die Events message und messageerror.Beispiele
Grundprinzip: zwei verbundene Ports
const channel = new MessageChannel();
// Über onmessage empfangen – dabei wird der Port automatisch gestartet
channel.port2.onmessage = (event) => {
console.log('port2 empfängt:', event.data);
event.target.postMessage('Antwort von port2');
};
channel.port1.onmessage = (event) => {
console.log('port1 empfängt:', event.data);
};
channel.port1.postMessage('Hallo von port1');
Private Verbindung zu einem iframe
const iframe = document.querySelector('iframe');
const channel = new MessageChannel();
// port1 behalten wir selbst
channel.port1.addEventListener('message', (event) => {
console.log('Vom iframe:', event.data);
});
channel.port1.start(); // bei addEventListener nötig!
iframe.addEventListener('load', () => {
// port2 wird übertragen (Transfer-Liste), nicht kopiert
iframe.contentWindow.postMessage('init', 'https://widget.example.com', [channel.port2]);
});
// Im iframe:
// window.addEventListener('message', (e) => {
// const port = e.ports[0];
// port.postMessage('Bereit!');
// });
Anfrage/Antwort mit einem Service Worker als Promise
const askServiceWorker = (message) =>
new Promise((resolve, reject) => {
const { port1, port2 } = new MessageChannel();
port1.onmessage = ({ data }) => {
port1.close();
data.error ? reject(new Error(data.error)) : resolve(data);
};
navigator.serviceWorker.controller.postMessage(message, [port2]);
});
// Im Service Worker:
// self.addEventListener('message', (e) => {
// e.ports[0].postMessage({ version: '1.4.2' });
// });
const info = await askServiceWorker({ type: 'GET_VERSION' });
console.log(info.version);
Aufgabe als Makrotask planen (ohne setTimeout-Mindestverzögerung)
const { port1, port2 } = new MessageChannel();
const queue = [];
port1.onmessage = () => {
const task = queue.shift();
task?.();
};
const scheduleTask = (fn) => {
queue.push(fn);
port2.postMessage(null);
};
scheduleTask(() => console.log('Makrotask ausgeführt'));
Promise.resolve().then(() => console.log('Mikrotask zuerst'));
console.log('Synchron');
// Wichtig · Fallstricke
- start() nicht vergessen: Wenn ihr Nachrichten mit
addEventListener('message', …)empfangt, müsst ihrport1.start()aufrufen, sonst bleiben die Nachrichten in der Warteschlange. Beim Setzen vononmessagewird der Port automatisch gestartet. - Übertragen statt kopieren: Ports müssen in der Transfer-Liste von
postMessage()stehen. Nach dem Übertragen ist der Port im sendenden Kontext unbrauchbar („neutered“). Wurdeport1übertragen, dürft ihr ihn lokal nicht weiterverwenden. - Aufräumen: Ports mit aktivem Listener werden nicht vom Garbage Collector freigegeben. Ruft
port1.close()auf, sobald die Kommunikation beendet ist, um Speicherlecks zu vermeiden. - Sicherheit: Wer einen Port hat, kann über ihn Nachrichten senden. Prüft beim ersten Handshake über
window.postMessageimmerevent.originund gebt als Ziel-Origin nie'*'an, wenn ihr einen Port übertragt. Validiert eingehende Daten, bevor ihr sie etwa perinnerHTMLausgebt. - Kompatibilität: Wird von allen modernen Browsern und in Workern unterstützt. In Node.js ist
MessageChannelseit Version 15 global verfügbar (vorher überrequire('worker_threads')). Dort verhindert ein offener Port, dass sich der Prozess beendet – mitport.unref()oderclose()lässt sich das vermeiden. - Nicht klonbare Daten (z. B. Funktionen oder DOM-Knoten) lösen beim Senden eine
DataCloneError-Ausnahme aus. Daten, die sich beim Empfänger nicht deserialisieren lassen, lösen dort das Eventmessageerroraus.