postMessage() між потоками
postMessage() це метод, який дозволяє надіслати повідомлення з одного контексту в інший, наприклад з головного потоку у Web Worker або навпаки. Він не виконує прямого доступу до пам'яті, а передає копію даних асинхронно, через механізм message passing (обмін повідомленнями).
Теорія
TL;DR
postMessage()кладе повідомлення в чергу іншого контексту і одразу повертає керування, він нічого не повертає.- Дані серіалізуються алгоритмом Structured Clone, тобто інший бік отримує копію, а не посилання.
- Отримувач читає дані в обробнику події
messageчерезevent.data. - Transferable об'єкти (
ArrayBufferта подібні) передають владіння без копіювання, це у 10 до 100 разів швидше на великих даних. - Зв'язок двосторонній: обидва боки можуть і слати, і приймати.
- Метод універсальний: Web Worker, iframe, інші вкладки, Service Worker.
Швидкий приклад
// main.js
const worker = new Worker('worker.js');
// надсилаємо повідомлення воркеру
worker.postMessage({ task: 'sum', a: 5, b: 7 });
// отримуємо відповідь
worker.onmessage = (event) => {
console.log('Worker reply:', event.data);
};// worker.js
onmessage = (event) => {
const { task, a, b } = event.data;
if (task === 'sum') {
postMessage(a + b); // надсилаємо результат назад
}
};Як це працює з Web Worker
Що відбувається крок за кроком:
- Головний потік викликає
worker.postMessage(...). - Повідомлення серіалізується і потрапляє в чергу повідомлень воркера.
- У воркері спрацьовує подія
onmessage. - Воркер виконує обробку і відповідає через
postMessage(). - Головний потік ловить цю подію через
onmessage.
Усе це відбувається асинхронно: JavaScript не чекає, доки воркер відповість.
Зв'язок працює в обидва боки:
| Звідки | Метод | Слухач |
|---|---|---|
| Головний потік у воркер | worker.postMessage() | onmessage у воркері |
| Воркер у головний потік | postMessage() | worker.onmessage |
Приклад «відлуння» (echo worker)
// worker.js
onmessage = (event) => {
postMessage(`Відлуння: ${event.data}`);
};// main.js
worker.postMessage('Привіт!');
worker.onmessage = (e) => console.log(e.data);
// → "Відлуння: Привіт!"Що саме можна передати
Передавати можна:
- примітиви (
string,number,boolean,null,undefined); - об'єкти (JSON-сумісні);
ArrayBuffer,Blob,File,ImageBitmap;- Transferable об'єкти, які передаються без копіювання.
Transferable Objects
Зазвичай дані копіюються під час передачі між потоками (через structured clone). Але для великих бінарних даних це дорого.
Transferable Objects (наприклад,
ArrayBuffer) можна «передати за посиланням», без копіювання, просто передавши владіння.
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]); // передаємо як transferableПісля передачі buffer у головному потоці стає невалідним, бо владіння перейшло воркеру. На великих даних це у 10 до 100 разів швидше.
Що повертає postMessage()
Нічого. Він просто ставить повідомлення в чергу іншого боку, а отримувач ловить його через подію message:
worker.onmessage = (event) => {
console.log(event.data);
};postMessage не тільки для Web Workers
Метод універсальний, він працює в різних контекстах браузера:
| Де використовується | Приклад |
|---|---|
| window у iframe | iframe.contentWindow.postMessage(data, origin) |
| iframe у parent | window.parent.postMessage(data, origin) |
| між вкладками | otherWindow.postMessage(data, origin) |
| Service Worker і головний потік | navigator.serviceWorker.controller.postMessage(data) |
| Web Worker і головний потік | worker.postMessage(data) |
Приклад window.postMessage() між сторінками
// Page A
const popup = window.open('https://example.com/popup.html');
popup.postMessage('Hello, page B!', 'https://example.com');
// Page B
window.addEventListener('message', (event) => {
if (event.origin === 'https://example.com') {
console.log('Message received:', event.data);
}
});Це дуже зручно для безпечної взаємодії між різними доменами, без порушення політики походження.
Як це влаштовано під капотом
- Коли викликається
postMessage(), дані серіалізуються за допомогою Structured Clone Algorithm. - Копія даних потрапляє в чергу отримувача.
- Коли потік звільняється, браузер викликає обробник
onmessage(event). - Дані доступні в
event.data.
Що всередині event
onmessage = (event) => {
console.log(event.data); // самі дані
console.log(event.origin); // джерело (для вікон та iframe)
console.log(event.ports); // MessagePort, якщо є
console.log(event.source); // хто надіслав повідомлення
};Проста аналогія:
Уявіть, що
postMessage()це поштова служба між офісами. Кожен офіс (потік JS) працює ізольовано. Коли один хоче передати дані іншому, він кладе лист (повідомлення) у скриньку (postMessage). Інший офіс отримує лист тоді, коли має на це час (onmessage).
Підсумок:
| Що робить | Пояснення |
|---|---|
postMessage() | Надсилає повідомлення між потоками та контекстами |
| Працює асинхронно | Не блокує код |
| Передає дані копією | Через structured clone |
| Оптимізація | Transferable об'єкти, без копіювання |
| Двосторонній зв'язок | Обидва потоки можуть слати і приймати |
| Де використовується | Web Workers, iframe, вікна, Service Workers |
Типові помилки
- Думати, що інший бік отримає той самий об'єкт. Він отримує копію, тож зміни в ньому не видно відправнику.
- Використовувати
bufferпісля того, як його передали як transferable. Він уже невалідний, бо владіння перейшло іншому потоку. - Не перевіряти
event.originуwindow.addEventListener('message', ...). Повідомлення може надіслати будь-яка сторінка, і без перевірки це діра в безпеці. - Передавати
'*'як targetOrigin уpostMessage(data, '*')для чутливих даних. Треба вказувати конкретне походження. - Передавати функції, DOM-вузли або екземпляри класів з методами. Structured clone їх не підтримує і кине
DataCloneError. - Очікувати, що
postMessage()щось поверне або що відповідь прийде синхронно. Він повертаєundefined, а відповідь прийде окремою подією.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.