基礎使用javascript
動態內聯workerhtml
subworkerjava
你們好,今天在這裏簡單介紹一下如何動態的建立內聯的web workers。git
在介紹worker-loader
時,咱們已經瞭解到能夠經過Blob
和createObjectURL
來建立內聯的web worker,這使得web worker的使用變得更加靈活。github
接下來,讓咱們一塊兒來對此進行嘗試。新建workify.js
,而且編寫咱們的頁面代碼:web
// index.html
<script type="text/javascript" src="./workify.js"></script>
<script type="text/javascript">
function add(a, b) {
return a + b;
}
async function initFunc() {
const workerAdd = workify(add);
console.log('workerAdd', await workerAdd(23, 16));
}
initFunc();
</script>
複製代碼
咱們但願經過workify
方法建立一個內聯的web worker的代理,而且能夠用async/await
的形式來調用這個方法。數組
接着咱們將編寫咱們的workify
方法。首先添加一些工具方法。bash
// workify.js
(function (g) {
const toString = function toString(t) {
return Function.prototype.toString.call(t);
};
const getId = function getId() {
return (+new Date()).toString(32);
};
...
const workify = function workify(target) {
...
}
g.workify = workify;
})(window);
複製代碼
接下來,咱們將建立內聯代碼的web worker:app
const code = `(${toString(proxy)})(${toString(target)})`;
const blob = new Blob([code]);
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
複製代碼
這裏咱們拼接的代碼,將會把目標函數做爲參數傳給咱們的proxy
函數,proxy
函數會負責處理web worker調用目標函數並與主線程進行通訊(經過調用postMessage
和設置onmessage
)。async
接下來,在workify
中咱們將設置worker
的onmessage
方法。同時咱們將爲worker
添加一個send
方法,這個方法會使用postMessage
發出消息,並返回一個Promise
。
最後workify
會返回一個方法,這個方法會經過worker.send
發送消息並返回它的Promise
:
worker.onmessage = function (ev) {
//
};
worker.send = function ({ type, data }) {
//
}
const rtn = function rtn(...args) {
return worker.send({
type: 'exec',
data: args,
});
};
return rtn;
複製代碼
由於咱們須要在worker
完成任務時知道須要去resolve
哪一個Promise
,所以咱們將在postMessage
中發送一個id
,並由worker
再返回來:
worker._cbs = {};
worker.onmessage = function (ev) {
const { type, id, data } = ev.data;
if (type === 'exec') {
worker._cbs[id](data);
}
};
worker.send = function ({ type, data }) {
return new Promise((res) => {
const id = getId();
worker._cbs[id] = (data) => {
res(data);
};
worker.postMessage({
id,
type,
data,
});
});
}
複製代碼
以後咱們再來實現proxy
方法,既web worker端的邏輯:
const proxy = function proxy(target) {
self.onmessage = function (ev) {
const { type, data, id } = ev.data;
let rtn = null;
if (type === 'exec') {
rtn = target.apply(null, data);
}
self.postMessage({
id,
type,
data: rtn,
});
};
};
複製代碼
咱們使用接收到的參數來調用目標函數,並將結果和id
發送回去。
若是須要經過importScripts
加載代碼,咱們能夠在目標函數中直接使用importScripts
,或將須要加載的代碼數組做爲另外一個參數傳入proxy:
const proxy = function proxy(target, scripts) {
if (scripts && scripts.length) importScripts.apply(self, scripts);
...
}
複製代碼
如上,咱們已經能夠將函數內聯爲web worker。接下來,我還但願能將Class
一樣內聯爲web worker。
class Adder {
constructor(initial) {
this.count = initial;
}
add(a) {
this.count += a;
return this.count;
}
}
async function initClass() {
let WAdder = workify(Adder);
let instance = await new WAdder(5);
console.log('apply add', await instance.add(7));
console.log('get count', await instance.count);
}
initClass();
複製代碼
首先,咱們改變rtn
的代碼,以判斷其是否經過new
調用:
const rtn = function rtn(...args) {
if (this instanceof rtn) {
return worker.send({
type: 'create',
data: args,
});
} else {
return worker.send({
type: 'exec',
data: args,
});
}
};
複製代碼
接下來咱們修改work.onmessage
,根據事件類型作出不一樣處理(在此處不一樣的僅create
事件):
worker.onmessage = function (ev) {
const { type, id, data } = ev.data;
if (type === 'create') {
worker._cbs[id](_proxy(worker));
} else {
worker._cbs[id](data);
}
};
複製代碼
咱們將先支持如下4類事件:
相應的proxy
函數中定義的onmessage
也要修改:
self.onmessage = function (ev) {
const { type, data, id } = ev.data;
let rtn = null;
if (type === 'exec') {
rtn = target.apply(null, data);
} else if (type === 'create') {
instance = new target(...data);
} else if (type === 'get') {
rtn = instance;
for (let p of data) {
rtn = rtn[p];
}
} else if (type === 'apply') {
rtn = instance;
for (let p of data.path) {
rtn = rtn[p];
}
rtn = rtn.apply(instance, data.data);
}
...
};
複製代碼
對應的邏輯分別爲生成示例、獲取屬性與調用方法。
在worker.onmessage
中,咱們經過_proxy(worker)
來返回一個代理,這是比較tricky的一段代碼。
咱們但願咱們返回的代理對象,能夠得到任意獲取屬性、任意調用代碼,並將調用web worker相應的行爲。
所以這裏咱們使用了Proxy
,而且其目標是一個函數,這樣咱們就能代理期get
(獲取屬性)和apply
(調用)兩種行爲。在get
中,咱們經過遞歸的使用_proxy
來實現深度代理。咱們經過path
來記錄當前路徑,當獲取的屬性爲then
時,例如await instance.count
中path
爲['count']
,咱們將使用worker.send
來獲取相應的屬性並返回其then
;而若當前path
爲空,咱們能夠直接返回null
,表示當前對象非thenable
並中斷Promise
鏈。
const _proxy = function _proxy(worker, path) {
path = path || [];
return new Proxy(function(){}, {
get: (_, prop, receiver) => {
if (prop === 'then') {
if (path.length === 0) return null;
const p = worker.send({
type: 'get',
data: path,
});
return p.then.bind(p);
}
return _proxy(worker, path.concat(prop));
},
apply: (_0, _1, args) => {
return worker.send({
type: 'apply',
data: {
path,
data: args,
},
});
},
});
};
複製代碼
今天介紹瞭如何經過Blob
來建立內聯的web workers。接下來將要介紹一下如何實現與subworker類似的功能。
(function (g) {
const toString = function toString(t) {
return Function.prototype.toString.call(t);
};
const getId = function getId() {
return (+new Date()).toString(32);
};
const proxy = function proxy(target, scripts) {
if (scripts && scripts.length) importScripts.apply(self, scripts);
let instance;
self.onmessage = function (ev) {
const { type, data, id } = ev.data;
let rtn = null;
if (type === 'exec') {
rtn = target.apply(null, data);
} else if (type === 'create') {
instance = new target(...data);
} else if (type === 'get') {
rtn = instance;
for (let p of data) {
rtn = rtn[p];
}
} else if (type === 'apply') {
rtn = instance;
for (let p of data.path) {
rtn = rtn[p];
}
rtn = rtn.apply(instance, data.data);
}
self.postMessage({
id,
type,
data: rtn,
});
};
};
const _proxy = function _proxy(worker, path) {
path = path || [];
return new Proxy(function(){}, {
get: (_, prop, receiver) => {
if (prop === 'then') {
if (path.length === 0) return null;
const p = worker.send({
type: 'get',
data: path,
});
return p.then.bind(p);
}
return _proxy(worker, path.concat(prop));
},
apply: (_0, _1, args) => {
return worker.send({
type: 'apply',
data: {
path,
data: args,
},
});
},
});
};
const workify = function workify(target, scripts) {
const code = `(${toString(proxy)})(${toString(target)}, ${JSON.stringify(scripts)})`;
const blob = new Blob([code]);
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
worker._cbs = {};
worker.onmessage = function (ev) {
const { type, id, data } = ev.data;
if (type === 'exec') {
worker._cbs[id](data);
} else if (type === 'create') {
worker._cbs[id](_proxy(worker));
} else if (type === 'apply') {
worker._cbs[id](data);
} else if (type === 'get') {
worker._cbs[id](data);
}
};
worker.send = function ({ type, data }) {
return new Promise((res) => {
const id = getId();
worker._cbs[id] = (data) => {
res(data);
};
worker.postMessage({
id,
type,
data,
});
});
}
const rtn = function rtn(...args) {
if (this instanceof rtn) {
return worker.send({
type: 'create',
data: args,
});
} else {
return worker.send({
type: 'exec',
data: args,
});
}
};
return rtn;
};
g.workify = workify;
})(window);
複製代碼