/**
* @file A host for a (mostly) generic Web Worker.
*
* @version 0.2.0
*/
let _instances = 0;
/**
* @class Manages a WebWorker instance and responds via events and/or
* promises.
* @extends EventDispatcher
*/
class WorkerHost extends EventDispatcher {
/**
* Creates a new WorkerHost instance and automatically instantiates an
* associated Web Worker.
*
* @param {String} scriptURL The external Web Worker script URL / path
* to create this instance with.
* @param {Boolean} onEventReady If true, the WorkerHost will dispatch an
* event (using the parent {@link EventDispatcher}), whenever the host
* ready state changes. It is advisable to enable this setting as the
* worker script may require some time to initialize and may be unable
* to respond to requests.
*/
constructor (scriptURL, eventOnReady) {
super();
this._scriptURL = scriptURL;
this._ready = false;
this._worker = new Worker(scriptURL);
this._worker._host = this; //ensure that this reference is set for events!
this._worker.addEventListener("message", this.onWorkerReady);
_instances++;
this._instanceNum = WorkerHost.instances;
this._eventOnReady = eventOnReady;
}
/**
* Event handler that responds to the associated Web Worker's "ready" event.
*
* @param {Object} event A standard Worker "message" event object.
*
* @listens Worker#message
* @see {@link dispatchReadyEvent}
*/
onWorkerReady (event) {
//this function is invoked in the Worker context, not WorkerHost
this._host._worker.removeEventListener("message", this._host.onWorkerReady);
if (event.data.ready) {
this._host._ready = true;
if (this._host._eventOnReady) {
this._host.dispatchReadyEvent(true);
}
} else {
throw (new Error("Worker responded with false ready state."));
}
}
/**
* @property {Array} instances Returns all WorkerHost instances in the current
* execution context.
* @static
*/
static get instances () {
return (_instances);
}
/**
* @property {Array} instanceNum The instance number of the current WorkerHost
* instance. This value matches the index of the instance within the
* {@link WorkerHost.instances} property.
*/
get instanceNum() {
return (this._instanceNum);
}
/**
* @property {String} scriptURL The Web Worker script URL associated with this
* host instance.
* @readonly
*/
get scriptURL () {
return (this._scriptURL);
}
/**
* @property {Boolean} ready True if the host instance is ready to accept
* a new request for the associated Web Worker.
* @readonly
*/
get ready() {
return (this._ready);
}
/**
* Invokes an asynchronous worker method using a free {@link workerHost}
* instance.
*
* @param {String} method The method to invoke in the hosted Worker instance.
* @param {Object} params The parameters to invoke the method with.
* @param {*} [requestID=undefined] A request ID that can be used to track the
* request over its lifetime (useful when re-assembling multiple discrete
* invokations that could otherwise result in a race condition).
*
* @example
* //simple example invoking an "add" function with parameters "num1" and "num2"
* let host = new WorkerHost("./workers/MyWorker.js");
* host.invoke("add", {num1:1, num2:3}, 1).then(event => {
* //event.data.requestID will match the invoking request ID (1 in this case)
* console.log ("1 + 3 = " + event.data.result);
* });
*
* @example
* //using an async function
* let host = new WorkerHost("./workers/MyWorker.js");
* async function doAdd(num1, num2) {
* //request ID is not included in this case
* return (host.invoke("add", {"num1":num1, "num2":num2}));
* }
* doAdd(1,3).then(event => {
* console.log ("1 + 3 = "+event.data.result);
* })
*
*/
async invoke (method, params, requestID) {
if (!this._ready) {
throw (new Error("Worker is not ready."));
}
this.dispatchReadyEvent(false);
if (this.useEvents) {
this._worker._host = this; //important for event handler!
this._worker.addEventListener("message", this.handleWorkerMessage);
}
this._worker.postMessage({"method":method, "params":params, "requestID":requestID});
if (!this.useEvents) {
while (true) {
let event = await this._worker.onEventPromise("message");
this.dispatchReadyEvent(true);
return (event);
}
} else {
return (true);
}
}
/**
* Dispatches a ready / not ready event for the host.
*
* @param {Boolean} isReady Defines the ready state of the host to dispatch.
*
* @fires ready
* @fires busy
*/
dispatchReadyEvent(isReady) {
if (isReady) {
this._ready = true;
var event = new Event("ready");
event.source = this;
this.dispatchEvent(event);
} else {
this._ready = false;
event = new Event("busy");
event.source = this;
this.dispatchEvent(event);
}
}
/**
* Handles a response message event for the associated Web Worker.
*
* @param {Object} event A standard Worker "message" event.
*
* @listens Worker#message
*/
handleWorkerMessage(event) {
//this function is invoked in the Worker context, not WorkerHost
this._host.removeEventListener("message", this.handleWorkerMessage);
var newEvent = new Event("message");
//perform a naive clone of the original event
for (var item in event) {
try {
newEvent[item] = event[item];
} catch (err) {}
}
this._host.dispatchEvent(newEvent);
this._host.dispatchReadyEvent(true);
}
}