基础使用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;
}
})();
复制代码