Web Workers 为前端提供了多线程执行代码的可能,在 worker 内做 CPU 密集型任务再合适不过了。
worker 中的代码在隔离的环境执行,无法访问、操作 DOM,避免了资源竞争,与宿主文档的通信走消息通道。
消息机制的优势是解耦,但不适用于拿消息结果的场景,比如:
A -> B
A -> C
A -> D
上例中 A 连续向 B、C、D 各发送了一条消息,B、C、D 处理完任务后再给 A 以回应,在 A 看来,收到的消息可能是这样:
X -> A
X -> A
X -> A
A 并不知道哪个 X 对应 B 的响应,同理,C、D 也无法对号入座。
如何解决呢?一种解决方式是为消息带上目的标识,worker 往回发消息时同样带上自身标识。
做完各种边界情况的处理后,就是通用的远程调用(简易)解决方案了:RPC
定义类型:
interface WorkerMessage {
id: number;
}
export interface WorkerCallMessage extends WorkerMessage {
method: string;
args: any[];
}
export interface WorkerReturnMessage extends WorkerMessage {
value?: any;
error?: any;
}
client
import { WorkerReturnMessage } from './type';
const worker = new Worker(new URL('./worker', import.meta.url));
class SimpleRPCClient {
static __instance: SimpleRPCClient;
static getInstance() {
if (!this.__instance) {
this.__instance = new SimpleRPCClient();
}
return this.__instance;
}
private messageId = 0;
private listeners: { [key: string]: {
resolve: (val: any) => void;
reject: (reason: any) => void;
} } = {};
private constructor() {
worker.addEventListener('message', this.onMessage);
}
private onMessage = (event: MessageEvent<WorkerReturnMessage>) => {
const { id, value, error } = event.data;
const { resolve, reject } = this.listeners[id];
if (error && reject) {
reject(error);
} else if (value && resolve) {
resolve(value);
}
delete this.listeners[id];
}
call<A extends any[] = any[], R = any>(method: string, ...args: A): Promise<R> {
return new Promise((resolve, reject) => {
const id = this.messageId++;
this.listeners[id] = { resolve, reject };
worker.postMessage({ id, method, args });
});
}
}
worker
import { WorkerCallMessage } from './type';
self.addEventListener('message', (event: MessageEvent<WorkerCallMessage>) => {
const { id, method, args } = event.data;
if (method === 'xxx') {
self.postMessage({
id,
value: 'xxxx'
})
}
});