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.
		
		
		
		
		
			
		
			
	
	
		
			121 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			TypeScript
		
	
		
		
			
		
	
	
			121 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			TypeScript
		
	
| 
											4 years ago
										 | 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 _utilWorker: Worker; | ||
|  | 
 | ||
|  |   constructor(path: string, timeout = WORKER_TIMEOUT) { | ||
|  |     this._utilWorker = new Worker(path); | ||
|  |     this.timeout = timeout; | ||
|  |     this._jobs = Object.create(null); | ||
|  |     this._DEBUG = false; | ||
|  |     this._jobCounter = 0; | ||
|  | 
 | ||
|  |     this._utilWorker.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) { | ||
|  |         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._utilWorker.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`); | ||
|  |         return reject(error); | ||
|  |       }, | ||
|  |     }; | ||
|  |   } | ||
|  | 
 | ||
|  |   private _removeJob(id: number) { | ||
|  |     if (this._DEBUG) { | ||
|  |       this._jobs[id].complete = true; | ||
|  |     } else { | ||
|  |       // tslint:disable-next-line: no-dynamic-delete
 | ||
|  |       delete this._jobs[id]; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   private _getJob(id: number) { | ||
|  |     return this._jobs[id]; | ||
|  |   } | ||
|  | } |