在Web Worker中运行一个模块。
尝试 workderize
, 在 调试console
let worker = window.workerize(`
export function add(a, b) {
// block for half a second to demonstrate asynchronicity
let start = Date.now();
while (Date.now()-start < 500);
return a + b;
}
`);
(async () => {
console.log('3 + 9 = ', await worker.add(3, 9));
console.log('1 + 2 = ', await worker.add(1, 2));
})();
在本目录,启动http
python
python -m SimpleHTTPServer
javascript
npx http-server .
因为只有一个文件
按照给出的例子,和作者描述
-
将一个小型的专门构建的RPC实现捆绑到您的应用程序中
-
如果导出的模块方法已经是异步的,那么签名是不变的
-
支持同步和异步工作者功能
-
与异步/等待美妙地工作
代码 22-34
export default function workerize(code) {
let exports = {};
let exportsObjName = `__EXPORTS_${Math.random().toString().substring(2)}__`; // 随机 id
if (typeof code==='function') code = `(${toCode(code)})(${exportsObjName})`;
// 如果直接输出函数,从我们例子来看,先不管
code = toCjs(code, exportsObjName, exports);
// 那么现在先去看 toCjs <----- 1
code += `\n(${toCode(setup)})(self, ${exportsObjName}, {})`;
// <--- 2 把 setup 函数加进来
let blob = new Blob([code], {
type: 'application/javascript'
}),
url = URL.createObjectURL(blob), // 变成了可以被加载的 Url
worker = new Worker(url),
counter = 0,
callbacks = {};
函数变文本
改造文本,记录函数
- code += setup
Blob 对象表示不可变的类似文件对象的原始数据。jsbin 例子�
Worker()
构造函数创建一个 Worker 对象,该对象执行指定的URL脚本。这个脚本必须遵守 同源策略 。
代码 35-44
worker.kill = signal => {
worker.postMessage({ type: 'KILL', signal });
setTimeout(worker.terminate);
}; // 关闭
let term = worker.terminate;
worker.terminate = () => {
URL.revokeObjectURL(url); // 丢 url
term(); // 触发本身结束命令
}; //
worker.rpcMethods = {};
代码 84-95
setup(worker, worker.rpcMethods, callbacks); // <---- 1
worker.call = (method, params) => new Promise( (resolve, reject) => {
let id = `rpc${++counter}`;
callbacks[id] = { method, resolve, reject };
worker.postMessage({ type: 'RPC', id, method, params });
});
// exports 通过 toCjs 函数 获取到了 函数名
// exports = {
// 'add' : true
// }
for (let i in exports) {
// i == 'add'
if (exports.hasOwnProperty(i) && !(i in worker)) {
worker[i] = (...args) => worker.call(i, args);
}
}
return worker;
-
wroker[i]
还记得 我们例子的使用 worker.add
// usage
(async () => {
console.log('3 + 9 = ', await worker.add(3, 9)); // args 3,9
console.log('1 + 2 = ', await worker.add(1, 2)); // args 1,2
})();
// index.js
worker[i] = (...args) => worker.call(i, args); // args=
worker.call
使用函数,通知 worker 内部,
worker.call = (method, params) => new Promise( (resolve, reject) => { // 变成了 异步/Promise
let id = `rpc${++counter}`; // 创建 唯一id
callbacks[id] = { method, resolve, reject }; // 放入本地缓存函数集
worker.postMessage({ type: 'RPC', id, method, params }); // 第一次触发 onmessage 事件
// type 自定义类型, id 唯一标签, method 函数, params 变量
});
代码 45-83
ctx == worker , rpcMethods == {}, callbacks == {}
function setup(ctx, rpcMethods, callbacks) {
/*
ctx.expose = (methods, replace) => {
if (typeof methods==='string') {
rpcMethods[methods] = replace;
}
else {
if (replace===true) rpcMethods = {};
Object.assign(rpcMethods, methods);
}
};
*/
ctx.addEventListener('message', ({ data }) => {
if (data.type==='RPC') {
let id = data.id;
if (id!=null) {
if (data.method) {
let method = rpcMethods[data.method]; // 本地缓存-rpcMethods-中找出 method
if (method==null) {
ctx.postMessage({ type: 'RPC', id, error: 'NO_SUCH_METHOD' });
}
else {
Promise.resolve()
.then( () => method.apply(null, data.params) )
.then( result => { ctx.postMessage({ type: 'RPC', id, result }); })
// 触发ctx.onmessage 事件
// data == { type: 'RPC', id, result }); }
.catch( error => { ctx.postMessage({ type: 'RPC', id, error }); });
// 触发ctx.onmessage 事件
// data == { type: 'RPC', id, error }); }
}
}
else {
let callback = callbacks[id]; // 本地缓存-函数集-id
if (callback==null) throw Error(`Unknown callback ${id}`);
delete callbacks[id];
if (data.error) callback.reject(Error(data.error));
else callback.resolve(data.result);
}
}
}
});
}
addEventListener('message', //...)
Worker
- 属性
onmessage 一个事件监听函数,每当拥有 message 属性的 MessageEvent 从 worker 中冒泡出来时就会执行该函数。
事件的 data 属性存有消息内容。
-
data.type === RPC
-
ctx.postMessage
向 worker 的内部作用域内传递消息。触发
ctx.onmessage
事件
因为 ctx.postMessage
在 onmessage
触发事件内部,所以第一次触发,不会来自内部
function toCode(func) {
return Function.prototype.toString.call(func);
}
- 定义
function add(){
}
- 运行
Function.prototype.toString.call(add) // 变成 String
- 结果
"function add(){}"
- code : String
// String
export function add(a, b) {
// block for half a second to demonstrate asynchronicity
let start = Date.now();
while (Date.now()-start < 500);
return a + b;
}
- exportsObjName : String
__EXPORTS_${Math.random().toString().substring(2)}__
"EXPORTS_4706166142920267"
- exports : Object
{}
function toCjs(code, exportsObjName, exports) {
exportsObjName = exportsObjName || 'exports';
exports = exports || {};
code = code.replace(/^(\s*)export\s+default\s+/m, (s, before) => {
// export default function
exports.default = true; // 具有默认导出函数
// before == ''
return `${before}${exportsObjName}.default = `;
// code 将变成 __EXPORTS_4706166142920267__.default = function ...
// 但是 这个例子 没有 default
});
code = code.replace(/^(\s*)export\s+(function|const|let|var)(\s+)([a-zA-Z$_][a-zA-Z0-9$_]*)/m, (s, before, type, ws, name) => {
exports[name] = true;
return `${before}${exportsObjName}.${name} = ${type}${ws}${name}`;
});
// code ==
// `__EXPORTS_4706166142920267__.add = function add(a, b) {
// // block for half a second to demonstrate asynchronicity
// let start = Date.now();
// while (Date.now()-start < 500);
// return a + b;
// }`
return `var ${exportsObjName} = {};\n${code}\n${exportsObjName};`;
// return
// var __EXPORTS_4706166142920267__ = {};
// `__EXPORTS_4706166142920267__.add = function add(a, b) {
// // block for half a second to demonstrate asynchronicity
// let start = Date.now();
// while (Date.now()-start < 500);
// return a + b;
// }`
// __EXPORTS_4706166142920267__
}
replace()
方法返回一个由替换值替换一些或所有匹配的模式后的新字符串。
模式可以是一个字符串或者一个正则表达式, 替换值可以是一个字符串或者一个每次匹配都要调用的函数。
code.replace(/^(\s*)export\s+default\s+/m,)
^
开头
\s
匹配一个空白符,包括空格、制表符、换页符、换行符和其他 Unicode
空格。
( )
(\s)
匹配 \s
并且捕获匹配项。 这被称为捕获括号(capturing parentheses)。
*
匹配前面的模式 \s
0 或多次。
export
直白匹配 export
文本
+
匹配前面的模式 \s
1 或多次。
/m
多行; 将开始和结束字符(^和$)视为在多行上工作(也就是,分别匹配每一行的开始和结束(由 \n 或 \r 分割),而不只是只匹配整个输入字符串的最开始和最末尾处。
code.replace(/^(\s*)export\s+(function|const|let|var)(\s+)([a-zA-Z$_][a-zA-Z0-9$_]*)
(function|const|)
匹配 function
或 const
[a-zA-Z$_]
一个字符集合,也叫字符组。匹配集合中的任意一个字符。你可以使用连字符'-'指定一个范围。
比如这个就是 小写的 a到z
大写 的A到Z
$
和 _
var ${exportsObjName} = {};\n${code}\n${exportsObjName};
我们回顾一下,作者提到这个库的特点
-
将一个小型的专门构建的
RPC
实现捆绑到您的应用程序中 -
如果导出的模块方法已经是
async
,那么签名是不变的 -
支持
sync
和async
工作者功能 -
与
async
/await
美妙地工作
-
RPC 远程过程调用协议
? -
我看到的签名,应该是
唯一 id
或者是exportsObjName
这个随机值 -
-
worker[i] = (...args) => worker.call(i, args);
的使用 -
worker.call = (method, params) => new Promise(
的定义,必定是异步
-
-
上面也说了,
Promise
,自然与async/await 美妙结合
真的厉害:)
作者使用的 构建工具
是 零配置的,power by rollup
的自己写的工具。