threads
Version:
Web workers & worker threads as simple as a function call
145 lines (144 loc) • 6.72 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.spawn = void 0;
const debug_1 = __importDefault(require("debug"));
const observable_fns_1 = require("observable-fns");
const common_1 = require("../common");
const promise_1 = require("../promise");
const symbols_1 = require("../symbols");
const master_1 = require("../types/master");
const invocation_proxy_1 = require("./invocation-proxy");
const debugMessages = debug_1.default("threads:master:messages");
const debugSpawn = debug_1.default("threads:master:spawn");
const debugThreadUtils = debug_1.default("threads:master:thread-utils");
const isInitMessage = (data) => data && data.type === "init";
const isUncaughtErrorMessage = (data) => data && data.type === "uncaughtError";
const initMessageTimeout = typeof process !== "undefined" && process.env.THREADS_WORKER_INIT_TIMEOUT
? Number.parseInt(process.env.THREADS_WORKER_INIT_TIMEOUT, 10)
: 10000;
function withTimeout(promise, timeoutInMs, errorMessage) {
return __awaiter(this, void 0, void 0, function* () {
let timeoutHandle;
const timeout = new Promise((resolve, reject) => {
timeoutHandle = setTimeout(() => reject(Error(errorMessage)), timeoutInMs);
});
const result = yield Promise.race([
promise,
timeout
]);
clearTimeout(timeoutHandle);
return result;
});
}
function receiveInitMessage(worker) {
return new Promise((resolve, reject) => {
const messageHandler = ((event) => {
debugMessages("Message from worker before finishing initialization:", event.data);
if (isInitMessage(event.data)) {
worker.removeEventListener("message", messageHandler);
resolve(event.data);
}
else if (isUncaughtErrorMessage(event.data)) {
worker.removeEventListener("message", messageHandler);
reject(common_1.deserialize(event.data.error));
}
});
worker.addEventListener("message", messageHandler);
});
}
function createEventObservable(worker, workerTermination) {
return new observable_fns_1.Observable(observer => {
const messageHandler = ((messageEvent) => {
const workerEvent = {
type: master_1.WorkerEventType.message,
data: messageEvent.data
};
observer.next(workerEvent);
});
const rejectionHandler = ((errorEvent) => {
debugThreadUtils("Unhandled promise rejection event in thread:", errorEvent);
const workerEvent = {
type: master_1.WorkerEventType.internalError,
error: Error(errorEvent.reason)
};
observer.next(workerEvent);
});
worker.addEventListener("message", messageHandler);
worker.addEventListener("unhandledrejection", rejectionHandler);
workerTermination.then(() => {
const terminationEvent = {
type: master_1.WorkerEventType.termination
};
worker.removeEventListener("message", messageHandler);
worker.removeEventListener("unhandledrejection", rejectionHandler);
observer.next(terminationEvent);
observer.complete();
});
});
}
function createTerminator(worker) {
const [termination, resolver] = promise_1.createPromiseWithResolver();
const terminate = () => __awaiter(this, void 0, void 0, function* () {
debugThreadUtils("Terminating worker");
// Newer versions of worker_threads workers return a promise
yield worker.terminate();
resolver();
});
return { terminate, termination };
}
function setPrivateThreadProps(raw, worker, workerEvents, terminate) {
const workerErrors = workerEvents
.filter(event => event.type === master_1.WorkerEventType.internalError)
.map(errorEvent => errorEvent.error);
// tslint:disable-next-line prefer-object-spread
return Object.assign(raw, {
[symbols_1.$errors]: workerErrors,
[symbols_1.$events]: workerEvents,
[symbols_1.$terminate]: terminate,
[symbols_1.$worker]: worker
});
}
/**
* Spawn a new thread. Takes a fresh worker instance, wraps it in a thin
* abstraction layer to provide the transparent API and verifies that
* the worker has initialized successfully.
*
* @param worker Instance of `Worker`. Either a web worker, `worker_threads` worker or `tiny-worker` worker.
* @param [options]
* @param [options.timeout] Init message timeout. Default: 10000 or set by environment variable.
*/
function spawn(worker, options) {
return __awaiter(this, void 0, void 0, function* () {
debugSpawn("Initializing new thread");
const timeout = options && options.timeout ? options.timeout : initMessageTimeout;
const initMessage = yield withTimeout(receiveInitMessage(worker), timeout, `Timeout: Did not receive an init message from worker after ${timeout}ms. Make sure the worker calls expose().`);
const exposed = initMessage.exposed;
const { termination, terminate } = createTerminator(worker);
const events = createEventObservable(worker, termination);
if (exposed.type === "function") {
const proxy = invocation_proxy_1.createProxyFunction(worker);
return setPrivateThreadProps(proxy, worker, events, terminate);
}
else if (exposed.type === "module") {
const proxy = invocation_proxy_1.createProxyModule(worker, exposed.methods);
return setPrivateThreadProps(proxy, worker, events, terminate);
}
else {
const type = exposed.type;
throw Error(`Worker init message states unexpected type of expose(): ${type}`);
}
});
}
exports.spawn = spawn;
;