Skip to main content

postMessage() між потоками

postMessage() це метод, який дозволяє надіслати повідомлення з одного контексту в інший, наприклад з головного потоку у Web Worker або навпаки. Він не виконує прямого доступу до пам'яті, а передає копію даних асинхронно, через механізм message passing (обмін повідомленнями).

Теорія

TL;DR

  • postMessage() кладе повідомлення в чергу іншого контексту і одразу повертає керування, він нічого не повертає.
  • Дані серіалізуються алгоритмом Structured Clone, тобто інший бік отримує копію, а не посилання.
  • Отримувач читає дані в обробнику події message через event.data.
  • Transferable об'єкти (ArrayBuffer та подібні) передають владіння без копіювання, це у 10 до 100 разів швидше на великих даних.
  • Зв'язок двосторонній: обидва боки можуть і слати, і приймати.
  • Метод універсальний: Web Worker, iframe, інші вкладки, Service Worker.

Швидкий приклад

javascript
// 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); };
javascript
// worker.js onmessage = (event) => { const { task, a, b } = event.data; if (task === 'sum') { postMessage(a + b); // надсилаємо результат назад } };

Як це працює з Web Worker

Що відбувається крок за кроком:

  1. Головний потік викликає worker.postMessage(...).
  2. Повідомлення серіалізується і потрапляє в чергу повідомлень воркера.
  3. У воркері спрацьовує подія onmessage.
  4. Воркер виконує обробку і відповідає через postMessage().
  5. Головний потік ловить цю подію через onmessage.

Усе це відбувається асинхронно: JavaScript не чекає, доки воркер відповість.

Зв'язок працює в обидва боки:

ЗвідкиМетодСлухач
Головний потік у воркерworker.postMessage()onmessage у воркері
Воркер у головний потікpostMessage()worker.onmessage

Приклад «відлуння» (echo worker)

javascript
// worker.js onmessage = (event) => { postMessage(`Відлуння: ${event.data}`); };
javascript
// 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) можна «передати за посиланням», без копіювання, просто передавши владіння.

javascript
const buffer = new ArrayBuffer(1024); worker.postMessage(buffer, [buffer]); // передаємо як transferable

Після передачі buffer у головному потоці стає невалідним, бо владіння перейшло воркеру. На великих даних це у 10 до 100 разів швидше.

Що повертає postMessage()

Нічого. Він просто ставить повідомлення в чергу іншого боку, а отримувач ловить його через подію message:

javascript
worker.onmessage = (event) => { console.log(event.data); };

postMessage не тільки для Web Workers

Метод універсальний, він працює в різних контекстах браузера:

Де використовуєтьсяПриклад
window у iframeiframe.contentWindow.postMessage(data, origin)
iframe у parentwindow.parent.postMessage(data, origin)
між вкладкамиotherWindow.postMessage(data, origin)
Service Worker і головний потікnavigator.serviceWorker.controller.postMessage(data)
Web Worker і головний потікworker.postMessage(data)

Приклад window.postMessage() між сторінками

javascript
// 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); } });

Це дуже зручно для безпечної взаємодії між різними доменами, без порушення політики походження.

Як це влаштовано під капотом

  1. Коли викликається postMessage(), дані серіалізуються за допомогою Structured Clone Algorithm.
  2. Копія даних потрапляє в чергу отримувача.
  3. Коли потік звільняється, браузер викликає обробник onmessage(event).
  4. Дані доступні в event.data.

Що всередині event

javascript
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, а відповідь прийде окремою подією.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.