You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			129 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			129 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			TypeScript
		
	
| const WORKER_TIMEOUT = 60 * 1000; // one minute
 | |
| 
 | |
| class TimedOutError extends Error {
 | |
|   constructor(message: string) {
 | |
|     super(message);
 | |
|     this.name = this.constructor.name;
 | |
|     if (typeof Error.captureStackTrace === 'function') {
 | |
|       Error.captureStackTrace(this, this.constructor);
 | |
|     } else {
 | |
|       this.stack = new Error(message).stack;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| export class WorkerInterface {
 | |
|   private readonly timeout: number;
 | |
|   private readonly _DEBUG: boolean;
 | |
|   private _jobCounter: number;
 | |
|   private readonly _jobs: Record<number, any>;
 | |
|   private readonly _worker: Worker;
 | |
| 
 | |
|   constructor(path: string, timeout = WORKER_TIMEOUT) {
 | |
|     (process as any).dlopen = () => {
 | |
|       throw new Error('Load native module is not safe');
 | |
|     };
 | |
| 
 | |
|     this._worker = new Worker(path);
 | |
|     this.timeout = timeout;
 | |
|     this._jobs = Object.create(null);
 | |
|     this._DEBUG = false;
 | |
|     this._jobCounter = 0;
 | |
| 
 | |
|     this._worker.onmessage = e => {
 | |
|       const [jobId, errorForDisplay, result] = e.data;
 | |
| 
 | |
|       const job = this._getJob(jobId);
 | |
|       if (!job) {
 | |
|         throw new Error(
 | |
|           `Received worker reply to job ${jobId}, but did not have it in our registry!`
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       const { resolve, reject, fnName } = job;
 | |
| 
 | |
|       if (errorForDisplay) {
 | |
|         // eslint:disable: no-console
 | |
| 
 | |
|         window?.log?.error(`Error received from worker job ${jobId} (${fnName}):`, errorForDisplay);
 | |
|         return reject(
 | |
|           new Error(`Error received from worker job ${jobId} (${fnName}): ${errorForDisplay}`)
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       return resolve(result);
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   public async callWorker(fnName: string, ...args: any) {
 | |
|     const jobId = this._makeJob(fnName);
 | |
| 
 | |
|     return new Promise((resolve, reject) => {
 | |
|       this._worker.postMessage([jobId, fnName, ...args]);
 | |
| 
 | |
|       this._updateJob(jobId, {
 | |
|         resolve,
 | |
|         reject,
 | |
|         args: this._DEBUG ? args : null,
 | |
|       });
 | |
| 
 | |
|       setTimeout(() => {
 | |
|         reject(new TimedOutError(`Worker job ${jobId} (${fnName}) timed out`));
 | |
|       }, this.timeout);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   private _makeJob(fnName: string): number {
 | |
|     this._jobCounter += 1;
 | |
|     const id = this._jobCounter;
 | |
| 
 | |
|     if (this._DEBUG) {
 | |
|       window.log.info(`Worker job ${id} (${fnName}) started`);
 | |
|     }
 | |
|     this._jobs[id] = {
 | |
|       fnName,
 | |
|       start: Date.now(),
 | |
|     };
 | |
| 
 | |
|     return id;
 | |
|   }
 | |
| 
 | |
|   private _updateJob(id: number, data: any) {
 | |
|     const { resolve, reject } = data;
 | |
|     const { fnName, start } = this._jobs[id];
 | |
| 
 | |
|     this._jobs[id] = {
 | |
|       ...this._jobs[id],
 | |
|       ...data,
 | |
|       resolve: (value: any) => {
 | |
|         this._removeJob(id);
 | |
|         const end = Date.now();
 | |
|         if (this._DEBUG) {
 | |
|           window.log.info(`Worker job ${id} (${fnName}) succeeded in ${end - start}ms`);
 | |
|         }
 | |
|         return resolve(value);
 | |
|       },
 | |
|       reject: (error: any) => {
 | |
|         this._removeJob(id);
 | |
|         const end = Date.now();
 | |
|         window.log.info(
 | |
|           `Worker job ${id} (${fnName}) failed in ${end - start}ms with ${error.message}`
 | |
|         );
 | |
|         return reject(error);
 | |
|       },
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   private _removeJob(id: number) {
 | |
|     if (this._DEBUG) {
 | |
|       this._jobs[id].complete = true;
 | |
|     } else {
 | |
|       delete this._jobs[id];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private _getJob(id: number) {
 | |
|     return this._jobs[id];
 | |
|   }
 | |
| }
 |