我想让一个Web-Worker管理自己的状态,同时为多个异步请求提供服务。
worker.ts文件
let a =0; //this is my worker's state
let worker=self as unknown as Worker;
worker.onmessage =(e)=>{
console.log("Rec msg", e.data);
if(e.data === "+1"){
setTimeout(()=>{
a=a+1;
worker.postMessage(a);
},3000);
}else if(e.data=== "+2"){
setTimeout(()=>{
a=a+2;
worker.postMessage(a);
},1000)
}
}
这是我的主文件:main.ts
let w =new Worker("./worker.ts", {type: "module"})
let wf =async (op: string)=>{
w.postMessage(op);
return new Promise<any>((res,rej)=>{
w.onmessage=res;
});
}
(async()=>{
let f1 = await wf("+1");
console.log("f1",f1.data);
})();
(async()=>{
let f2 = await wf("+2");
console.log("f2",f2.data);
})()
只有 f2
返回,并且 f1
丢失.我已经使用超时来模拟比如说一些异步任务由工人自己完成。
我怎样才能同时收到 f1
和 f2
?
你的问题是,你试图将一个基于事件的API作为一个基于Promise的API使用,但事件可能会多次触发,而Promise应该只解析一次。
Worker和主线程之间的通信是通过发送和接收消息来实现的,但默认情况下,这些消息之间没有一对一的关系。通信的两端(端口)会简单地堆叠接收到的消息,并在有时间的时候依次处理它们。
在你的代码中,主线程的 worker.onmessage
的处理者 f1
已被第二个调用覆盖 f2
同步(一个微任务之后,但这对我们来说仍然是同步的)。你可以在事件中使用 addEventListener
方法,至少这样就不会被覆盖。但即便如此,当第一个 讯息 事件将在 worker
,两个处理者都会认为是自己的信息到达了,而事实上是自己的信息到达了。f2
. 所以这不是你需要的... ...
你需要的是建立一个通信协议,允许两端识别每个任务。例如,你可以用一个包含以下内容的对象来包装你所有任务的数据 .UIID
成员,要确保两端都这样封装消息,然后从主线程检查该UUID来解析相应的Promise。
但是这样实现和使用起来会变得有点复杂。
我个人最喜欢的方式是在主线程中创建一个新的 消息频道 每个任务。如果你不知道这个API,我邀请你读一读 本回答 解释我的基础知识。
由于我们确信只有一个信息会通过这个。消息频道 是Worker对我们发送给它的任务的响应,我们可以像等待Promise一样等待它。
我们所要做的,就是确保在 Worker 线程中,我们通过传输的端口而不是全局范围来响应。
const url = getWorkerURL();
const w = new Worker(url)
let wf =async (op)=>{
// we create a new MessageChannel
const channel = new MessageChannel();
// we transfer one of its ports to the Worker thread
w.postMessage(op, [channel.port1]);
return new Promise((res,rej)=>{
// we listen for a message from the remaining port of our MessageChannel
channel.port2.onmessage=res;
});
}
(async()=>{
let f1 = await wf("+1");
console.log("f1",f1.data);
})();
(async()=>{
let f2 = await wf("+2");
console.log("f2",f2.data);
})()
// SO only
function getWorkerURL() {
const elem = document.querySelector( '[type="worker-script"]' );
const script = elem.textContent;
const blob = new Blob( [script], { type: "text/javascript" } );
return URL.createObjectURL( blob );
}
<script type="worker-script">
let a = 0;
const worker = self;
worker.onmessage =(e)=>{
const port = e.ports[0]; // this is where we will respond
if(e.data === "+1"){
setTimeout(()=>{
a=a+1;
// we respond through the 'port'
port.postMessage(a);
},3000);
}else if(e.data=== "+2"){
setTimeout(()=>{
a=a+2;
// we respond through the 'port'
port.postMessage(a);
},1000)
}
};
</script>