今天與明天會介紹一些與 Web worker
相關的套件,今天介紹的 comlink 是 google
團隊開發的套件,目的是讓 Web worker
使用起來更為方便,以下我們直接藉由範例來看使用 comlink
前後的差別
在原本的一般方法中會使用 worker.postMessage
將資料傳到 worker
線程進行運算
// 主線程
const worker = new Worker('public/no-comlink-worker.js');
document.querySelector('button').onclick = () => {
const plusInputs = document.querySelectorAll('input.plus');
const plusValues = Array.from(plusInputs).map((plusInput) => {
return +plusInput.value || 0;
});
// 將 [2, 3] 傳遞到 worker 進行加法運算
worker.postMessage({ type: 'plus', value: plusValues });
const multiplyInputs = document.querySelectorAll('input.multiply');
const multiplyValues = Array.from(multiplyInputs).map((multiplyInput) => {
return +multiplyInput.value || 0;
});
// 將 [2, 3] 傳遞到 worker 進行乘法運算
worker.postMessage({ type: 'multiply', value: multiplyValues });
};
接著 worker
線程需要根據傳來的 type
決定要進行哪種邏輯的運算
// worker 線程
self.onmessage = (e) => {
console.log("worker received data", e.data);
const { type, value } = e.data;
switch (type) {
case "plus": {
const [num1, num2] = value;
const result = num1 + num2;
self.postMessage({ type: "plus", result });
break;
}
case "multiply": {
const [num1, num2] = value;
const result = num1 * num2;
self.postMessage({ type: "multiply", result });
break;
}
default:
break;
}
};
然後主線程使用 worker.onmessage
接收 worker
運算完後的資料
// 主線程
worker.onmessage = (e) => {
const { type, result } = e.data;
switch (type) {
case 'plus': {
document.querySelector('.result-plus').textContent = result;
break;
}
case 'multiply': {
document.querySelector('.result-multiply').textContent = result;
break;
}
default:
break;
}
};
我們可以看到當有不同邏輯需要在 worker
做處理時 (加法 plus 與 乘法 multiply),都需要額外多傳 type
,以及加一堆 switch case
來判斷執行的是哪種邏輯,以及 主線程接收到資料後 (onmessage) 也需要多判斷 type
來決定傳回來的數值要怎麼處理,當 worker
中要處理的邏輯及函式變多的時候,每次這樣用 type
決定要執行哪段程式碼是很麻煩的,而使用 comlink
可以簡化這一塊部分,以下讓我們看看用 comlink
改寫後的程式碼
首先我們先來看 worker
中的程式碼,在 worker
中不再使用 self.onmessage
接收訊息,反之用的是一個簡單的 calculate
物件,並把需要運算的邏輯定義出來,最終使用 Comlink.expose(calculate)
,讓主線程可以直接取用到 calculate
物件
// worker 線程
importScripts("https://unpkg.com/comlink/dist/umd/comlink.js");
const calculate = {
plus(value) {
const [num1, num2] = value;
return num1 + num2;
},
multiply(value) {
const [num1, num2] = value;
return num1 * num2;
}
};
Comlink.expose(calculate);
接著在主線程中可以直接使用 Comlink.wrap(worker)
方式取用到 calculate
物件,並可以直接呼叫底下的方法進行運算,底下的每個方法都被封裝成 promise
的回傳值,所以可以很方便的拿到 worker
執行完後的結果
// 主線程
import * as Comlink from "https://unpkg.com/comlink/dist/esm/comlink.mjs";
const worker = new Worker("public/worker.js");
// 取用到 calculate 物件
const calculate = Comlink.wrap(worker);
document.querySelector("button").onclick = async () => {
const plusInputs = document.querySelectorAll("input.plus");
const plusValues = Array.from(plusInputs).map((plusInput) => {
return +plusInput.value || 0;
});
// 呼叫 plus 方法
const plusResult = await calculate.plus(plusValues);
document.querySelector(".result-plus").textContent = plusResult;
const multiplyInputs = document.querySelectorAll("input.multiply");
const multiplyValues = Array.from(multiplyInputs).map((multiplyInput) => {
return +multiplyInput.value || 0;
});
// 呼叫 multiply 方法
const multiplyResult = await calculate.multiply(multiplyValues);
document.querySelector(".result-multiply").textContent = multiplyResult;
};
使用 comlink
可以簡化 Web worker
中原本 postMessage
及 onMessage
繁複的寫法,使得程式更簡潔也更方便使用