addons/isl/src/ComparisonView/SplitDiffView/workerApi.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {CancellationToken} from 'shared/CancellationToken';
b69ab319
b69ab3110type WorkerModuleType<Request, Response> = {
b69ab3111 handleMessage: (callback: (msg: Response) => void, msg: MessageEvent<Request>) => void;
b69ab3112};
b69ab3113/**
b69ab3114 * Some environments, like jest tests, don't support WebWorkers.
b69ab3115 * Let's just import the equivalent file dynamically and call the functions
b69ab3116 * instead of doing message passing, so we can validate syntax highlighting in tests.
b69ab3117 */
b69ab3118export class SynchronousWorker<Request, Response> {
b69ab3119 private importedModulePromise: Promise<WorkerModuleType<Request, Response>> | undefined;
b69ab3120 constructor(
b69ab3121 private getImportedModulePromise: () => Promise<WorkerModuleType<Request, Response>>,
b69ab3122 ) {}
b69ab3123
b69ab3124 public getImportedModule(): Promise<WorkerModuleType<Request, Response>> {
b69ab3125 if (this.importedModulePromise) {
b69ab3126 return this.importedModulePromise;
b69ab3127 }
b69ab3128 this.importedModulePromise = this.getImportedModulePromise();
b69ab3129 return this.importedModulePromise;
b69ab3130 }
b69ab3131
b69ab3132 public onmessage = (_e: MessageEvent) => null;
b69ab3133 public postMessage(msg: Request) {
b69ab3134 this.getImportedModule().then(module => {
b69ab3135 module.handleMessage(
b69ab3136 (msg: Response) => {
b69ab3137 this.onmessage({data: msg} as MessageEvent<Response>);
b69ab3138 },
b69ab3139 {data: msg} as MessageEvent<Request>,
b69ab3140 );
b69ab3141 });
b69ab3142 }
b69ab3143
b69ab3144 public dispose(): void {
b69ab3145 return undefined;
b69ab3146 }
b69ab3147}
b69ab3148
b69ab3149export class WorkerApi<Request extends {type: string}, Response extends {type: string}> {
b69ab3150 private id = 0;
b69ab3151 private requests = new Map<number, (response: Response) => void>();
b69ab3152 private listeners = new Map<Response['type'], (msg: Response) => void>();
b69ab3153
b69ab3154 constructor(public worker: Worker) {
b69ab3155 type ResponseWithId = Response & {id: number};
b69ab3156 this.worker.onmessage = e => {
b69ab3157 const msg = e.data as Response;
b69ab3158 const id = (msg as ResponseWithId).id;
b69ab3159 const callback = this.requests.get(id);
b69ab3160 if (callback) {
b69ab3161 callback(msg);
b69ab3162 this.requests.delete(id);
b69ab3163 }
b69ab3164
b69ab3165 const listener = this.listeners.get(msg.type);
b69ab3166 if (listener) {
b69ab3167 listener(msg);
b69ab3168 }
b69ab3169 };
b69ab3170 }
b69ab3171
b69ab3172 /** Send a message, then wait for a reply */
b69ab3173 request<T extends Request['type']>(
b69ab3174 msg: Request & {type: T},
b69ab3175 cancellationToken?: CancellationToken,
b69ab3176 ): Promise<Response & {type: T}> {
b69ab3177 return new Promise<Response & {type: T}>(resolve => {
b69ab3178 const id = this.id++;
b69ab3179 this.worker.postMessage({...msg, id});
b69ab3180
b69ab3181 const disposeOnCancel = cancellationToken?.onCancel(() => {
b69ab3182 this.worker.postMessage({type: 'cancel', idToCancel: id});
b69ab3183 });
b69ab3184 this.requests.set(id, result => {
b69ab3185 (resolve as (response: Response) => void)(result);
b69ab3186 disposeOnCancel?.();
b69ab3187 });
b69ab3188 });
b69ab3189 }
b69ab3190
b69ab3191 /** listen for messages from the server of a given type */
b69ab3192 listen<T extends Response['type']>(
b69ab3193 type: T,
b69ab3194 listener: (msg: Response & {type: T}) => void,
b69ab3195 ): () => void {
b69ab3196 this.listeners.set(type, listener as (msg: Response) => void);
b69ab3197 return () => this.listeners.delete(type);
b69ab3198 }
b69ab3199
b69ab31100 dispose() {
b69ab31101 this.worker.terminate();
b69ab31102 }
b69ab31103}