JavaScript – Web Worker

前言

在上一篇 << 單線程 與 執行機制 >> 中, 我們提到了 Web Worker.

它的誕生是爲了解決 JS 主線程執行耗時計算時, 導致 UI 無法及時更新的卡死現象.

它的解決思路是把同步代碼異步化. 原本需要 JS 主線程執行的運算, 轉交給另一條線程去完成.

這和 setTimeout 是同一個原理. 計數也不是 JS 主線程做的, 而是定時器觸發線程做的.

 

參考

阮一峯 – Web Worker 使用教程

MDN – SharedWorker

 

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...

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章