Instantly share code, notes, and snippets.
Created
March 9, 2019 12:26
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save Bludwarf/91fe75558fd64717271e5f06cfb2e339 to your computer and use it in GitHub Desktop.
Adaptation of "worker.service.ts" by Daniel Amores for SharedWorker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///<reference path='../../../node_modules/@types/sharedworker/index.d.ts'/> | |
import {Injectable} from '@angular/core'; | |
function loadScript(url, callback) { | |
// Add a the script tag to the head | |
const head = document.getElementsByTagName('head')[0]; | |
const script = document.createElement('script'); | |
script.type = 'text/javascript'; | |
script.src = url; | |
// Bind the callback (depends on browser compatibility). | |
if ('onreadystatechange' in script) { | |
script['onreadystatechange'] = callback; | |
} | |
script.onload = callback; | |
// Load the script | |
head.appendChild(script); | |
} | |
// Credits to: https://github.com/start-javascript/ngx-web-worker/blob/master/web-worker.ts | |
/** | |
* <p><code>Service</code> that handles the <i>web worker</i> creation from the given input | |
* <code>Function</code>. It also handles the execution and "<i>promisification</i>" of the | |
* created web worker.</p> | |
*/ | |
@Injectable(/*{ | |
providedIn: 'root' | |
}*/) | |
export class SharedWorkerService { | |
// tslint:disable-next-line | |
private workerFunctionToUrlMap = new WeakMap<Function, string>(); | |
private promiseToWorkerMap = new WeakMap<Promise<any>, SharedWorker.SharedWorker>(); | |
/** | |
* <p>Method that runs the given function with the given data.</p> | |
* @param workerFunction function to run on the web worker context. | |
* @param data data to pass to the web worker context. <b>IMPORTANT</b>: | |
* the function must be self-contained, meaning that no external functions or | |
* libraries can be passed through this parameter. | |
*/ | |
public run<T>(workerFunction: (input: any) => T, data?: any): Promise<T> { | |
const url = this.getOrCreateWorkerUrl(workerFunction); | |
// loadScript(url, () => console.log('loadScript OK')); | |
return this.runUrl(url, data); | |
} | |
/** | |
* <p>Method that runs the given <i>ObjectURL</i> with the given data.</p> | |
* @param url <i>ObjectURL</i> to run on the web worker context. | |
* @param data data to pass to the web worker context. <b>IMPORTANT</b>: | |
* the function must be self-contained, meaning that no external functions or | |
* libraries can be passed through this parameter. | |
*/ | |
public runUrl(url: string, data?: any): Promise<any> { | |
// const worker = new SharedWorker(url); // FIXME dynamic | |
const worker = new SharedWorker('assets/shared_worker.js'); | |
const promise = this.createPromiseForWorker(worker, data); | |
const promiseCleaner = this.createPromiseCleaner(promise); | |
this.promiseToWorkerMap.set(promise, worker); | |
promise.then(promiseCleaner).catch(promiseCleaner); | |
return promise; | |
} | |
// tslint:disable | |
/** | |
* <p>Method that terminates the given <i>Promise</i> and removes it from the | |
* internal service maps.</p> | |
* @param promise promise to terminate. | |
*/ | |
public terminate<T>(promise: Promise<T>): Promise<T> { | |
return this.removePromise(promise); | |
} | |
/** | |
* <p>Method that retrieves the <i>web worker</i> to which the given <i>Promise</i> | |
* belongs to. | |
* @param promise promise whose <i>web worker</i> we want to find. | |
*/ | |
public getWorker(promise: Promise<any>): SharedWorker.SharedWorker { | |
return this.promiseToWorkerMap.get(promise); | |
} | |
// tslint:enable | |
/** | |
* <p>Method that handles the promise creation for the given <i>web worker</i> with | |
* the given input data.</p> | |
* @param worker worker for which the promise will be created. | |
* @param data data that will be passed into the worker object. | |
*/ | |
private createPromiseForWorker<T>(worker: SharedWorker.SharedWorker, data: any) { | |
return new Promise<T>((resolve, reject) => { | |
worker.port.addEventListener('message', (event) => resolve(event.data)); | |
worker.port.addEventListener('error', reject); | |
worker.port.start(); | |
worker.port.postMessage(data); | |
}); | |
} | |
// tslint:disable | |
/** | |
* <p>Method that allocates a <i>web worker</i> <i>ObjectURL</i> for the given function. | |
* It's used to create caches for the <i>(function, workerUrl)</i> pairs in order to avoid | |
* creating the urls more than once.</p> | |
* @param fn function whose worker we want to allocate. | |
*/ | |
private getOrCreateWorkerUrl(fn: Function): string { | |
if (!this.workerFunctionToUrlMap.has(fn)) { | |
const url = this.createWorkerUrl(fn); | |
this.workerFunctionToUrlMap.set(fn, url); | |
return url; | |
} | |
return this.workerFunctionToUrlMap.get(fn); | |
} | |
/** | |
* <p>Method that creates a <i>web worker</i> <i>ObjectURL</i> from the given | |
* <i>Function</i> object.</p> | |
* @param resolve function the <i>web worker</i> will run. | |
*/ | |
private createWorkerUrl(resolve: Function): string { | |
const resolveString = resolve.toString(); | |
// The template is basically an addEventListener attachment that creates a | |
// closure (IIFE*) with the provided function and invokes it with the provided | |
// data. | |
// * IIFE stands for immediately Immediately-Invoked Function Expression | |
// Removed the postMessage from this template in order to allow worker functions | |
// to use asynchronous functions and resolve whenever they need to. | |
const webWorkerTemplate = ` | |
onconnect = function(event) { | |
var port = event.ports[0]; | |
port.onmessage = function(event) { | |
((${resolveString})(e.data)); | |
} | |
} | |
`; | |
const blob = new Blob([webWorkerTemplate], { type: 'text/javascript' }); | |
return URL.createObjectURL(blob); | |
} | |
// tslint:enable | |
/** | |
* <p>Method that creates a function that removes the given promise from the | |
* service context.</p> | |
* @param promise promise the cleaner function will be created for. | |
*/ | |
private createPromiseCleaner<T>(promise: Promise<T>): (input: any) => T { | |
return (event) => { | |
this.removePromise(promise); | |
return event; | |
}; | |
} | |
/** | |
* <p>Method that removes the given promise from the service context. | |
* It also terminates the associated worker in case it exists.</p> | |
* @param promise promise to be removed from the service context. | |
*/ | |
private removePromise<T>(promise: Promise<T>): Promise<T> { | |
const worker = this.promiseToWorkerMap.get(promise); | |
if (worker) { | |
worker.port.close(); | |
} | |
this.promiseToWorkerMap.delete(promise); | |
return promise; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment