-
Notifications
You must be signed in to change notification settings - Fork 0
/
Worker.ts
92 lines (83 loc) · 2.61 KB
/
Worker.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import { Deferred, deferred } from "./Promise";
import { Result, error, ok } from "./Result";
type AnyMethods = { [name: string]: (...args: any[]) => Promise<any> | any };
export function defineWorker<Methods extends AnyMethods>(methods: Methods) {
addEventListener("message", async (event) => {
let [id, name, params] = event.data as [number, string, any[]];
let process = methods[name];
if (process == null)
return postMessage([id, error(`no such method '${name}'`)]);
try {
postMessage([id, ok(await process(...params))]);
} catch (err) {
postMessage([id, error(err)]);
}
});
}
export class WorkerManager<Methods extends AnyMethods, E = string> {
private _id: number;
private _worker: Promise<Worker> | null;
private _waiting: Map<number, Deferred<Result<any, E>>>;
private _create: () => Worker | Promise<Worker>;
constructor(create: () => Worker | Promise<Worker>) {
this._id = 0;
this._create = create;
this._worker = null;
this._waiting = new Map();
}
onmessage = (evt: MessageEvent) => {
let [id, result] = evt.data as [number, Result<any, E>];
let deferred = this._waiting.get(id);
if (deferred == null)
throw new Error(`WorkerManager: orphaned result ${id}`);
this._waiting.delete(id);
deferred.resolve(result);
};
get worker() {
if (this._worker == null) {
this._worker = Promise.resolve(this._create());
this._worker.then((worker) => {
worker.onmessage = this.onmessage;
});
}
return this._worker;
}
async submit<N extends keyof Methods>(
name: N,
params: Parameters<Methods[N]>
): Promise<Result<Awaited<ReturnType<Methods[N]>>, E>> {
let id = (this._id += 1);
let def = deferred<Result<any, E>>();
this._waiting.set(id, def);
(await this.worker).postMessage([id, name, params]);
return def.promise;
}
async terminate() {
if (this._worker != null) {
(await this._worker).terminate();
this._worker = null;
}
this._waiting.clear();
}
}
export function supportsWorkerModule(): boolean {
let supports = false;
const tester = {
get type() {
// it's been called, it's supported
supports = true;
return undefined as any;
},
};
try {
// We use "blob://" as url to avoid an useless network request.
// This will either throw in Chrome
// either fire an error event in Firefox
// which is perfect since
// we don't need the worker to actually start,
// checking for the type of the script is done before trying to load it.
new Worker("blob://", tester);
} finally {
return supports;
}
}