时间:2023-07-07 15:06:01 | 来源:网站运营
时间:2023-07-07 15:06:01 来源:网站运营
动态创建 Web Worker 实践指南:作为前端,在消费接口提供的数据时,往往由于数据实际分布在不同地方(如一部分存储在 ODPS ,而另一部分可能更适合在应用初始化时从本地载入内存)而需要对数据进行区分处理。当然,交互的实现可能也会需要很重的计算逻辑,而为了加速计算、不阻塞渲染线程,Web Worker 不失为一个很好的选择。Promise
链式调用替换相对较为繁琐的事件监听处理逻辑;main.js
用于 Worker()
构造器并处理与 worker 间消息的接受与发送: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
用于响应主线程的消息,包括处理与回传结果: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+ 等环境data:application/javascript
: 该方式适用于 Opera 10.60 - 12eval
: 适用于其他环境,比如 IE 10+Blob
对象?它表示一个不可变、原始数据的类文件对象,但不局限于 JavaScript 原生格式的数据,常被用来存储体量很大的二进制编码格式的数据。你可以使用 Blob()
构造函数从一段字符串中创建一个 Blob 对象:const debug = {hello: "world"};const blob = new Blob( [JSON.stringify(debug, null, 2)], {type : 'application/json'});
而利用 URL.createObjectURL()
API 我们可以将 Blob 对象转换为一个对象 URL 传入 Worker 构造函数,如下:const worker = new Worker(URL.createObjectURL(blob));
那么什么又是 data:application/javascript
呢?Data URLs,即前缀为 data:
协议的的URL,其允许内容创建者向文档中嵌入小文件。这样的 URL 由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身:data:[<mediatype>][;base64],<data>
所以现在你应该知道 application/javascript
所指了,是的,利用这个 URL 我们可以这样创建 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
作为向后兼容的一种方式,你可以这样创建 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 并向其发送一段文本,而 Worker 在处理完毕后主线程会把结果弹窗显示出来。接下来,我们就用它继续操作。const BASE_DATASETS = '';class DynamicWorker { constructor(worker) { /** * 依赖的全局变量声明 * 如果 BASE_DATASETS 非字符串形式,可调用 JSON.stringify 等方法进行处理 * 保证变量的正常声明 */ const CONSTS = `const base = ${BASE_DATASETS};`; /** * 数据处理函数 */ const formatFn = `const formatFn = ${worker.toString()};`; /** * 内部 onmessage 处理 */ const onMessageHandlerFn = `self.onmessage = ()=>{}`; /** * 返回结果 * @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); } /** * 动态调用 */ send(data) {} close() {}}
以上代码有几点需要解释下,比如生成 Blob
对象时,由于入参是字符串数组,如果只是调用 .toString()
,便无法拿到函数名,因此所有字符串采用变量命名的形式定义。接着我们调用 URL.createObjectURL
生成对象 URL,在创建完 Worker 后调用 URL.revokeObjectURL()
让浏览器知道不再需要对这个文件保持引用。URL.revokeObjectURL()
静态方法用来释放一个之前通过调用 URL.createObjectURL()
创建的已经存在的 URL 对象。当你结束使用某个 URL 对象时,应该通过调用这个方法来让浏览器知道不再需要保持这个文件的引用了。详见 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 的链式调用,我们可以隐藏较为琐碎的事件监听处理程序。来写一个 send
方法允许开发者动态调用,内部我们接收到数据后,改变 resolve 的状态,并返回这个 Promise:send(data) { const w = this.worker; w.postMessage({ data, }); return new Promise((res) => { this.resolve = res; })}
我们定义一个 this.resolve
用于记录 Promise 的状态,然后在 Worker 收到响应后便判断 this.resolve
然后决定是否 resolve 计算结果:const handleResult = ({ data: { data } }) => { if (this.resolve) { resolve(data); this.resolve = null; }}
如此一来,接下来我们就可以在主进程中这样调用 DynamicWorker
了:import DataWorker from './dynamicWorker.js';const formatFunc = () => { return { 'res': 'I am a customized result string.', }}const worker = new DataWorker(formatFunc);const result = []; // demo 数据worker.send({ method: 'format', data: result}).then((e) => { alert(`Response: ${JSON.stringify(e)}`);})
this.worker.addEventListener('message', handleResult);
这个事件监听处理函数是区分不出每次调用的,在收到消息后它只会执行 resolve。那么该如何解决呢?其实也较为简单,加入一个标志位用于区分不同调用即可。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; })}
最后,根据 flag
传参我们改写 Worker 内部的 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');}`;// ...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 非字符串形式,可调用 JSON.stringify 等方法进行处理 * 保证变量的正常声明 */ const CONSTS = `const base = ${BASE_DATASETS};`; /** * 数据处理函数 */ const formatFn = `const formatFn = ${worker.toString()};`; /** * 内部 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'); }`; /** * 返回结果 * @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); } /** * 动态调用 */ 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;
关键词:实践,指南,创建,动态