前言
在上一篇 << 單線程 與 執行機制 >> 中, 我們提到了 Web Worker.
它的誕生是爲了解決 JS 主線程執行耗時計算時, 導致 UI 無法及時更新的卡死現象.
它的解決思路是把同步代碼異步化. 原本需要 JS 主線程執行的運算, 轉交給另一條線程去完成.
這和 setTimeout 是同一個原理. 計數也不是 JS 主線程做的, 而是定時器觸發線程做的.
參考
Worker 基本使用
The Problem
假設我們有一個複雜運算 for loop 50億次 (耗時 5 秒)
document.querySelector('h1').textContent = 'Hello World'; for (let i = 0; i < 5_000_000_000; i++) {} // 需要 5 秒鐘 console.log('do something else');
如果我們讓 JS 主線程來處理, 那麼 UI 渲染就會慢 5 秒鐘, 用戶遲遲纔會看見 Hello World 出現.
New Web Worker
這時我們可以開啓 Web Worker, 它會創建另一條線程去處理這個 for loop. 然後我們把後續的代碼變成一個 callback.
worker.js
for (let i = 0; i < 5_000_000_000; i++) {} // 需要 5 秒鐘 self.postMessage('done'); // 通知主線程, 任務完成 self.close(); // 可以在子線程關閉, 也可以交由主線程關閉.
index.js
document.querySelector('h1').textContent = 'Hello World'; const worker = new Worker('./worker.js', { name: 'worker' }); // callback worker.addEventListener('message', () => { console.log('do something else'); worker.terminate(); // 關閉子線程 });
通過 addEventListener 註冊 callback, 這樣原本同步的代碼就變成異步了.
Worker 溝通
主線程和子線程 (worker) 的溝通是通過 postMessage 和 addEventListener('message') 完成的.
postMessage 類似於 dispatchEvent 的意思.
主線程和子線程都可以 postMessage 和 listen message, 也都可以關閉子線程.
主線程也能監聽子線程的錯誤
// index.ts – main thread worker.postMessage('message'); // dispatch event to child thread worker.addEventListener('message', () => {}); // listen event from child thread worker.addEventListener('error', () => {}); // listen error from child thread worker.terminate(); // close child thread // worker.ts – child thread self.postMessage('done'); // dispatch event to main thread self.addEventListener('message', () => {}); // listen event from main thread self.close(); // close child thread (把自己關了)
Message Data Type
// index.ts const worker = new Worker('./worker.js', { name: 'worker' }); const file = document.querySelector('input').files[0]; file.arrayBuffer().then(arrayBuffer => { worker.postMessage({ value: 'value', buffer: arrayBuffer }, { transfer: [arrayBuffer] }); }); // worker.ts self.addEventListener('message', event => { const { value, buffer } = event.data; });
兩個點要注意
1. postMessage 可以傳對象, 但是對象會被 deep copy.
2. ArrayBuffer 也會被 copy, 有時候 size 太大 copy 會很慢, 所以 postMessage 有個設置叫 transferable objects
聲明 transfer 以後, ArrayBuffer 就轉交給 worker 了 (no more copy, use cut instead), 這時主線程就不可以再讀取 ArrayBuffer了.
Import Script in Worker
self.importScripts('/imported-script.js');
worker 內可以 import 其它的 script.
imported-script.js 也可以調用 self.postMessage 和主線程溝通.
而 worker 和 imported-script 要溝通則是通過全局變量 e.g. self.value = 'some value'.
SharedWorker
目前支持率不是很好, 以後才研究 TODO...