UNPKG

threads

Version:

Web workers & worker threads as simple as a function call

145 lines (144 loc) 6.72 kB
"use strict"; 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;