輕鬆使用WebWorker,解放耗時較大的演算法程式碼
Web Worker為Web內容在後臺執行緒中執行指令碼提供了一種簡單的方法。執行緒可以執行任務而不干擾使用者介面。
基礎知識
MDN 講解的已經較詳細,此處不再重複。
常規用法
main.js
var first = document.querySelector('#number1');
var second = document.querySelector('#number2');
var result = document.querySelector('.result');
if (window.Worker) { // Check if Browser supports the Worker api.
// Requires script name as input
var myWorker = new Worker("worker.js");
// onkeyup could be used instead of onchange if you wanted to update the answer every time
// an entered value is changed, and you don't want to have to unfocus the field to update its .value
first.onchange = function() {
myWorker.postMessage([first.value,second.value]); // Sending message as an array to the worker
console.log('Message posted to worker');
};
second.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
};
myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Message received from worker');
};
}
複製程式碼
worker.js
onmessage = function(e) {
console.log('Message received from main script');
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
console.log('Posting message back to main script');
postMessage(workerResult);
}
複製程式碼
巧妙用法
通過使用封裝好的WebWorker類和整理後的worker.js:
- 簡化了對Worker類的呼叫;
- 無需進一步修改worker.js內部方法;
- 可以執行在任意指令碼檔案中的方法,無需copy到worker.js檔案中;
WebWorker類
WebWorker.js
// 封裝了一個WebWorker類
class WebWorker {
constructor(options)
{
if (window.Worker && options.workerUrl)
{
this.worker = new Worker(options.workerUrl);
this.onMessage();
this.onError(options && options.errorCallback);
}
else
{
alert('Browser does not support Worker, or workerUrl not set!');
}
}
static getInstance(options)
{
if (!this.instance)
{
this.instance = new WebWorker(options);
}
return this.instance;
}
onMessage()
{
this.worker.onmessage = function (e) {
if (e.data)
{
let {result, callback} = e.data;
call(callback, result);
}
else
{
alert(`onmessage error: ${e}!`);
}
// throw new Error('Something wrong!'); // onerror
};
}
postMessage(method = null, params = [], callback = null, scripts = [], isClose = false)
{
scripts = Array.isArray(scripts) ? scripts : [scripts];
this.worker.postMessage({method, params, callback, scripts});
}
onError(errorCallback)
{
this.worker.onerror = function (err) {
console.table(err);
errorCallback && errorCallback(err);
};
}
terminate()
{
this.worker.terminate();
}
}
/**
* A funtcion which can easily invoke function from a string method name
* @param method, eg: 'alert', 'math.floor', 'math.floor.toString'
* @param params
* @return {Promise<any>}
*/
const call = (method, params) => {
let callMethod = (callers, thisArg) => {
let caller = callers.shift();
thisArg = thisArg || window;
thisArg = thisArg[caller];
if (callers.length > 0)
{
thisArg = callMethod(callers, thisArg);
}
return thisArg;
};
return new Promise((resolve) => {
let callers = method.split('.');
params = Array.isArray(params) ? params : [params];
let result = callMethod(callers)(...params);
resolve(result);
});
};
複製程式碼
math.js: 耗時大,且會阻塞執行緒影響瀏覽器渲染的方法
function isPrime (number) => {
if (number === 0 || number === 1)
{
return true;
}
for (let i = 2; i <= Math.sqrt(number); i++)
{
if (number % i === 0)
{
return false;
}
}
return true;
};
複製程式碼
worker.js: 一個專用worker僅僅能被生成它的指令碼所使用。
const call = (method, params) => {
let callMethod = (callers, thisArg) => {
let caller = callers.shift();
thisArg = (thisArg ? thisArg : this)[caller];
if (callers.length > 0)
{
thisArg = callMethod(callers, thisArg);
}
return thisArg;
};
return new Promise((resolve) => {
let callers = method.split('.');
params = Array.isArray(params) ? params : [params];
let result = callMethod(callers)(...params);
resolve(result);
});
};
const applyMethod = (data) => {
let {method, params, scripts} = data;
scripts && importScripts.apply(this, scripts);
return call(method, params);
};
// WebWorker
onmessage = function (e) {
let {method, params, scripts, callback, isClose} = e.data;
applyMethod({method, params, scripts})
.then((result) => {
postMessage({result: result, callback: callback});
isClose && close();
});
};
複製程式碼
main.js: 引入到頁面,初始化WebWorker
const options = {
workerUrl: window.location.origin + '/worker.js'
};
// postMessage(methodName, params, callbackName, scripts, isClose);
WebWorker.getInstance(options).postMessage('isPrime', '1000001111111111', 'console.log', [window.location.origin + '/math.js']);
複製程式碼
通過main.js的呼叫方法,可以使任意檔案的方法被呼叫。
使用注意:
- workerUrl: 使用絕對地址或者相對WebWorker.js的地址(Worker是在WebWorker中初始化的);
- scripts: 被引入的script檔案必須是傳統JS庫;
“依賴傳統JS庫”意為著被依賴的JS檔案,很有可能不是 CMD,AMD,UMD或ES 6模組檔案。相反,所謂的傳統JS庫檔案通常是被包裝成為一個IIFE表示式(即,一個立即執行的大閉包),並且輸出一個全域性變數作為暴露API集的頂層名稱空間。