時間:2023-07-07 15:06:01 | 來源:網(wǎng)站運營
時間:2023-07-07 15:06:01 來源:網(wǎng)站運營
動態(tài)創(chuàng)建 Web Worker 實踐指南:作為前端,在消費接口提供的數(shù)據(jù)時,往往由于數(shù)據(jù)實際分布在不同地方(如一部分存儲在 ODPS ,而另一部分可能更適合在應用初始化時從本地載入內(nèi)存)而需要對數(shù)據(jù)進行區(qū)分處理。當然,交互的實現(xiàn)可能也會需要很重的計算邏輯,而為了加速計算、不阻塞渲染線程,Web Worker 不失為一個很好的選擇。Promise
鏈式調(diào)用替換相對較為繁瑣的事件監(jiān)聽處理邏輯;main.js
用于 Worker()
構(gòu)造器并處理與 worker 間消息的接受與發(fā)送:let myWorker = new Worker('worker.js');myWorker.postMessage('Hi, this is a message from main.js');myWorker.onmessage = (e) => { console.log('Message received from worker', JSON.stringify(e));}
一個 worker.js
用于響應主線程的消息,包括處理與回傳結(jié)果:onmessage = (e) => { console.log('Message received from main script'); let workerResult = `Result: ${JSON.stringify(e)}`; console.log('Posting message back to main script'); postMessage(workerResult);}
Blob
: 該方式適用于 Chrome 8+, Firefox 6+, Safari 6.0+, Opera 15+ 等環(huán)境data:application/javascript
: 該方式適用于 Opera 10.60 - 12eval
: 適用于其他環(huán)境,比如 IE 10+Blob
對象?它表示一個不可變、原始數(shù)據(jù)的類文件對象,但不局限于 JavaScript 原生格式的數(shù)據(jù),常被用來存儲體量很大的二進制編碼格式的數(shù)據(jù)。你可以使用 Blob()
構(gòu)造函數(shù)從一段字符串中創(chuàng)建一個 Blob 對象:const debug = {hello: "world"};const blob = new Blob( [JSON.stringify(debug, null, 2)], {type : 'application/json'});
而利用 URL.createObjectURL()
API 我們可以將 Blob 對象轉(zhuǎn)換為一個對象 URL 傳入 Worker 構(gòu)造函數(shù),如下:const worker = new Worker(URL.createObjectURL(blob));
那么什么又是 data:application/javascript
呢?Data URLs,即前綴為 data:
協(xié)議的的URL,其允許內(nèi)容創(chuàng)建者向文檔中嵌入小文件。這樣的 URL 由四個部分組成:前綴(data:)、指示數(shù)據(jù)類型的 MIME 類型、如果非文本則為可選的 base64 標記、數(shù)據(jù)本身:data:[<mediatype>][;base64],<data>
所以現(xiàn)在你應該知道 application/javascript
所指了,是的,利用這個 URL 我們可以這樣創(chuàng)建 Web Worker:const response = "onmessage=function(e){postMessage('Worker: '+e.data);}";const worker = new Worker( 'data:application/javascript,' + encodeURIComponent(response) );worker.onmessage = (e) => { alert('Response: ' + e.data);};worker.postMessage('Test');
而在 Safari (<6) 與 IE 10 中,eval
作為向后兼容的一種方式,你可以這樣創(chuàng)建 Web Worker:const response = "onmessage=function(e){postMessage('Worker: '+e.data);}";const worker = new Worker('Worker-helper.js');worker.postMessage(response);
其中 Worker-helper.js
代碼如下:onmessage = (e) => { onmessage = null; // Clean-up eval(e.data);};
當然,在使用之前還需要對相應 API 進行兼容判斷,比如 window.URL || window.webkitURL
或者 window.Worker
等,這里便不詳述。method='format'
的消息:window.URL = window.URL || window.webkitURL;const response = `onmessage = ({ data: { data } }) => { console.log('Message received from main script'); const {method} = data; if (data.data && method === 'format') { postMessage({ data: { 'res': 'I am a customized result string.', } }); } console.log('Posting message back to main script');}`;const blob = new Blob([response], {type: 'application/javascript'});const worker = new Worker( URL.createObjectURL(blob));// 事件處理worker.onmessage = (e) => { alert(`Response: ${JSON.stringify(e)}`);};worker.postMessage({ method: 'format', data: []});
這個 Demo 會建立一個 Web Worker 并向其發(fā)送一段文本,而 Worker 在處理完畢后主線程會把結(jié)果彈窗顯示出來。接下來,我們就用它繼續(xù)操作。const BASE_DATASETS = '';class DynamicWorker { constructor(worker) { /** * 依賴的全局變量聲明 * 如果 BASE_DATASETS 非字符串形式,可調(diào)用 JSON.stringify 等方法進行處理 * 保證變量的正常聲明 */ const CONSTS = `const base = ${BASE_DATASETS};`; /** * 數(shù)據(jù)處理函數(shù) */ const formatFn = `const formatFn = ${worker.toString()};`; /** * 內(nèi)部 onmessage 處理 */ const onMessageHandlerFn = `self.onmessage = ()=>{}`; /** * 返回結(jié)果 * @param {*} param0 */ const handleResult = () => {} const blob = new Blob( [`(()=>{${CONSTS}${formatFn}${onMessageHandlerFn}})()`], { type: 'text/javascript' } ); this.worker = new Worker(URL.createObjectURL(blob)); this.worker.addEventListener('message', handleResult); URL.revokeObjectURL(blob); } /** * 動態(tài)調(diào)用 */ send(data) {} close() {}}
以上代碼有幾點需要解釋下,比如生成 Blob
對象時,由于入?yún)⑹亲址當?shù)組,如果只是調(diào)用 .toString()
,便無法拿到函數(shù)名,因此所有字符串采用變量命名的形式定義。接著我們調(diào)用 URL.createObjectURL
生成對象 URL,在創(chuàng)建完 Worker 后調(diào)用 URL.revokeObjectURL()
讓瀏覽器知道不再需要對這個文件保持引用。URL.revokeObjectURL()
靜態(tài)方法用來釋放一個之前通過調(diào)用 URL.createObjectURL()
創(chuàng)建的已經(jīng)存在的 URL 對象。當你結(jié)束使用某個 URL 對象時,應該通過調(diào)用這個方法來讓瀏覽器知道不再需要保持這個文件的引用了。詳見 MDN API.onMessageHandlerFn
:const onMessageHandlerFn = `self.onmessage = ({ data: { data } }) => { console.log('Message received from main script'); const {method} = data; if (data.data && method === 'format') { self.postMessage({ data: formatFn(data.data) }); } console.log('Posting message back to main script');}`;
利用 Promise 的鏈式調(diào)用,我們可以隱藏較為瑣碎的事件監(jiān)聽處理程序。來寫一個 send
方法允許開發(fā)者動態(tài)調(diào)用,內(nèi)部我們接收到數(shù)據(jù)后,改變 resolve 的狀態(tài),并返回這個 Promise:send(data) { const w = this.worker; w.postMessage({ data, }); return new Promise((res) => { this.resolve = res; })}
我們定義一個 this.resolve
用于記錄 Promise 的狀態(tài),然后在 Worker 收到響應后便判斷 this.resolve
然后決定是否 resolve 計算結(jié)果:const handleResult = ({ data: { data } }) => { if (this.resolve) { resolve(data); this.resolve = null; }}
如此一來,接下來我們就可以在主進程中這樣調(diào)用 DynamicWorker
了:import DataWorker from './dynamicWorker.js';const formatFunc = () => { return { 'res': 'I am a customized result string.', }}const worker = new DataWorker(formatFunc);const result = []; // demo 數(shù)據(jù)worker.send({ method: 'format', data: result}).then((e) => { alert(`Response: ${JSON.stringify(e)}`);})
this.worker.addEventListener('message', handleResult);
這個事件監(jiān)聽處理函數(shù)是區(qū)分不出每次調(diào)用的,在收到消息后它只會執(zhí)行 resolve。那么該如何解決呢?其實也較為簡單,加入一個標志位用于區(qū)分不同調(diào)用即可。this.flagMapping = {};
簡單起見,我們直接取日期作為標志位 key,改寫后的 send 方法長成這樣:send(data) { const w = this.worker; const flag = new Date().toString(); w.postMessage({ data, flag, }); return new Promise((res) => { this.flagMapping[flag] = res; })}
最后,根據(jù) flag
傳參我們改寫 Worker 內(nèi)部的 onmessage
函數(shù)以及返回結(jié)果函數(shù)的判斷邏輯:const onMessageHandlerFn = `self.onmessage = ({ data: { data, flag } }) => { console.log('Message received from main script'); const {method} = data; if (data.data && method === 'format') { self.postMessage({ data: formatFn(data.data), flag }); } console.log('Posting message back to main script');}`;// ...const handleResult = ({ data: { data, flag } }) => { const resolve = this.flagMapping[flag]; if (resolve) { resolve(data); delete this.flagMapping[flag]; }}
const BASE_DATASETS = '';class DynamicWorker { constructor(worker) { /** * 依賴的全局變量聲明 * 如果 BASE_DATASETS 非字符串形式,可調(diào)用 JSON.stringify 等方法進行處理 * 保證變量的正常聲明 */ const CONSTS = `const base = ${BASE_DATASETS};`; /** * 數(shù)據(jù)處理函數(shù) */ const formatFn = `const formatFn = ${worker.toString()};`; /** * 內(nèi)部 onmessage 處理 */ const onMessageHandlerFn = `self.onmessage = ({ data: { data, flag } }) => { console.log('Message received from main script'); const {method} = data; if (data.data && method === 'format') { self.postMessage({ data: formatFn(data.data), flag }); } console.log('Posting message back to main script'); }`; /** * 返回結(jié)果 * @param {*} param0 */ const handleResult = ({ data: { data, flag } }) => { const resolve = this.flagMapping[flag]; if (resolve) { resolve(data); delete this.flagMapping[flag]; } } const blob = new Blob( [`(()=>{${CONSTS}${formatFn}${onMessageHandlerFn}})()`], { type: 'text/javascript' } ); this.worker = new Worker(URL.createObjectURL(blob)); this.worker.addEventListener('message', handleResult); this.flagMapping = {}; URL.revokeObjectURL(blob); } /** * 動態(tài)調(diào)用 */ send(data) { const w = this.worker; const flag = new Date().toString(); w.postMessage({ data, flag, }); return new Promise((res) => { this.flagMapping[flag] = res; }) } close() { this.worker.terminate(); }}export default DynamicWorker;
關鍵詞:實踐,指南,創(chuàng)建,動態(tài)
微信公眾號
版權所有? 億企邦 1997-2025 保留一切法律許可權利。