基礎使用git
動態內聯workergithub
subworkerweb
你們好,今天在這裏簡單介紹一下如何實現與subworker類似的功能。chrome
在web workers的標準中,還有一個概念叫subworker,這使得你能夠在web workers中建立web workers。但事實上若是你在例如chrome
或safari
瀏覽器中這麼作,會獲得相似Worker is not defined
這樣的報錯信息,即沒法在web worker內建立Worker
。即便這是一個自2010年即被報告的bug,但仍始終未被修復,所以若是你須要使用subworker,只能藉助其餘手段來實現相似的效果。瀏覽器
這裏咱們須要將web workers的建立(以及web workers之間的通訊)經過主線程進行代理。安全
假設咱們建立一個web worker(稱爲parent
),併爲p
建立一個subworkerchild
,加上主線程main
,若是咱們須要實現從主線程到worker再到subworker並返回的通訊歷程,即:bash
其中比較須要關注的是二、3即parent和child之間的通訊,這裏實際都是發送信息給main以後讓main來轉發信息的。併發
首先,咱們判斷當前代碼在worker仍是主線程中運行。由於web worker的種種限制,判斷方式有多種,例如嘗試調用document
,或是嘗試調用Worker
:post
(function () {
let inWorker = false;
try {
document;
} catch (_) {
inWorker = true;
}
const getId = function getId() {
return (+new Date()).toString(32);
};
if (inWorker) {
...
} else {
...
}
}
複製代碼
主線程中的代碼以下:ui
// main.js
const parent = new SubWorker('parent.js');
parent.postMessage('start');
parent.onmessage = (ev) => {
if (ev.data === 'success'){
console.log('p 2 m');
}
};
複製代碼
在主線程的環境下,咱們的SubWorker
只是對真正的worker作一層簡單的代理,例如postMessage
和terminate
都是直接調用真正的worker來執行操做。同時,咱們把全部worker都經過id
保存下引用:
const workers = {};
class SubWorker {
constructor (f, id, parentId) {
this.id = id || getId();
this.worker = new Worker(f);
this.worker.onmessage = this.handleMessage.bind(this);
this.parentId = parentId;
workers[this.id] = this;
}
handleMessage(ev) {
...
}
postMessage(data) {
this.worker.postMessage(data);
}
terminate() {
this.worker.terminate();
}
}
self.SubWorker = SubWorker;
複製代碼
這裏m2p
的消息發送已經實現了。
在建立worker時咱們指定了onmessage。若是咱們收到的是帶_subWorker
標誌的消息,則表明咱們須要處理worker內爲child worker代理的事件,包括新建worker、發送消息和終止worker等等。在新建child worker時,parent會指定id併發送過來,這樣在main和parent中就是用同一個id標記實際的worker和它的代理,這樣咱們就能讓main爲parent代理child的事件發送(data.type === 'msg'
)。同時,接收到指令的parent worker和由此建立的child worker的父子關係也被須要記錄下來:
handleMessage(ev) {
const data = ev.data;
if (!data._subWorker) {
...
}
if (data.type === 'create') {
const subWorker = new SubWorker(data.file, data.id, this.id);
} else if (data.type === 'msg') {
workers[data.id].postMessage(data.data);
} else if (data.type === 'terminate') {
workers[data.id].terminate();
}
}
複製代碼
接下來咱們再來看一下在worker中的SubWorker
,p2m
消息使用的是原生的postMessage
來發送的。parent worker接受消息使用了onMessage
而避開了原生了onmessage
,這裏的緣由以後會解釋的:
// parent.js
importScripts('./subworkers.js');
const subWorker = new SubWorker('child.js');
subWorker.onmessage = (ev) => {
if (ev.data === 'pong') {
console.log('c 2 p');
postMessage('success');
}
};
onMessage = (ev) => {
if (ev.data === 'start'){
console.log('m 2 p');
subWorker.postMessage('ping');
}
};
複製代碼
在worker中的SubWorker
只須要實現一些簡單的代理,發送帶有_subWorker
標誌位的消息給主線程:
if (!self.SubWorker) {
const workers = {};
class SubWorker {
constructor (f) {
this.id = getId();
workers[this.id] = this;
self.postMessage({
_subWorker: true,
type: 'create',
id: this.id,
file: f,
});
}
postMessage(data) {
self.postMessage({
_subWorker: true,
type: 'msg',
id: this.id,
data: data,
});
}
terminate() {
self.postMessage({
_subWorker: true,
type: 'terminate',
id: this.id,
});
}
}
self.SubWorker = SubWorker;
}
複製代碼
parent中SubWorker
的postMessage
發出的消息被主進程轉發給child worker,child worker用原生的onmessage
和postMessage
來收發消息,這樣p2c
的消息發送也完成了,而c2p
的消息發送須要進一步的處理。
c2p
的消息會在主線程中接收到,但這條消息是發送給parent而非main的,所以這裏須要轉發一下。當main
中的SubWorker
收到消息時,若是當前的SubWorker
沒有父級,那消息就是發給本身的,不然,實際的消息接受人應該是本身的父級worker,所以須要轉發一下消息。因而咱們須要修改主線程下SubWorker
的handleMessage
方法:
handleMessage(ev) {
const data = ev.data;
if (!data._subWorker) {
if (this.parentId) {
workers[this.parentId].postMessage({
_subWorker: true,
type: 'msg',
id: this.id,
data: data,
});
} else {
this.onmessage(ev);
}
return;
}
...
}
複製代碼
最後的部分是,處理m2p
和c2p
兩種消息的接收。這兩個消息都是在parent中接收的,一個是self.onMessage
,一個是subWorker.onmessage
。但接收消息的渠道只有self.onmessage
(或者經過self.addEventListener('message', cb)
),所以須要設置self.onmessage
,並將消息交給本身或subWorker
處理:
if (inWorker) {
if (!self.SubWorker) {
const workers = {};
self.onmessage = function onmessage(ev) {
const data = ev.data;
if (!data._subWorker) {
self.onMessage(ev);
return;
}
workers[data.id].onmessage(new MessageEvent('worker', {
data: data.data,
}));
};
...
}
}
複製代碼
如此,整個消息的接收、發送流程就走通了。
本身動手實現一下SubWorker
,對於實踐和鞏固代理模式的知識會是很是好的場景。
web workers是一個很是酷的東西,包括從未被實現的subworker、由於安全漏洞被關閉的SharedArrayBuffer等等特性。相比它的好哥們兒Service Workers的C位出道,隨着wasm愈來愈完善,也許web workers還沒火就要過氣了 (:
(function () {
let inWorker = false;
try {
document;
} catch (_) {
inWorker = true;
}
const getId = function getId() {
return (+new Date()).toString(32);
};
if (inWorker) {
if (!self.SubWorker) {
const workers = {};
self.onmessage = function onmessage(ev) {
const data = ev.data;
if (!data._subWorker) {
self.onMessage(ev);
return;
}
workers[data.id].onmessage(new MessageEvent('worker', {
data: data.data,
}));
};
class SubWorker {
constructor (f) {
this.id = getId();
workers[this.id] = this;
self.postMessage({
_subWorker: true,
type: 'create',
id: this.id,
file: f,
});
}
postMessage(data) {
self.postMessage({
_subWorker: true,
type: 'msg',
id: this.id,
data: data,
});
}
terminate() {
self.postMessage({
_subWorker: true,
type: 'terminate',
id: this.id,
});
}
}
self.SubWorker = SubWorker;
}
} else {
const workers = {};
class SubWorker {
constructor (f, id, parentId) {
this.id = id || getId();
this.worker = new Worker(f);
this.worker.onmessage = this.handleMessage.bind(this);
this.parentId = parentId;
workers[this.id] = this;
}
handleMessage(ev) {
const data = ev.data;
if (!data._subWorker) {
if (this.parentId) {
workers[this.parentId].postMessage({
_subWorker: true,
type: 'msg',
id: this.id,
data: data,
});
} else {
this.onmessage(ev);
}
return;
}
if (data.type === 'create') {
const subWorker = new SubWorker(data.file, data.id, this.id);
} else if (data.type === 'msg') {
workers[data.id].postMessage(data.data);
} else if (data.type === 'terminate') {
workers[data.id].terminate();
}
}
postMessage(data) {
this.worker.postMessage(data);
}
terminate() {
this.worker.terminate();
}
}
self.SubWorker = SubWorker;
}
})();
複製代碼