UNPKG

threads

Version:

Web workers & worker threads as simple as a function call

209 lines (208 loc) 9.22 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.expose = exports.isWorkerRuntime = exports.Transfer = exports.registerSerializer = void 0; const is_observable_1 = __importDefault(require("is-observable")); const common_1 = require("../common"); const transferable_1 = require("../transferable"); const messages_1 = require("../types/messages"); const implementation_1 = __importDefault(require("./implementation")); var common_2 = require("../common"); Object.defineProperty(exports, "registerSerializer", { enumerable: true, get: function () { return common_2.registerSerializer; } }); var transferable_2 = require("../transferable"); Object.defineProperty(exports, "Transfer", { enumerable: true, get: function () { return transferable_2.Transfer; } }); /** Returns `true` if this code is currently running in a worker. */ exports.isWorkerRuntime = implementation_1.default.isWorkerRuntime; let exposeCalled = false; const activeSubscriptions = new Map(); const isMasterJobCancelMessage = (thing) => thing && thing.type === messages_1.MasterMessageType.cancel; const isMasterJobRunMessage = (thing) => thing && thing.type === messages_1.MasterMessageType.run; /** * There are issues with `is-observable` not recognizing zen-observable's instances. * We are using `observable-fns`, but it's based on zen-observable, too. */ const isObservable = (thing) => is_observable_1.default(thing) || isZenObservable(thing); function isZenObservable(thing) { return thing && typeof thing === "object" && typeof thing.subscribe === "function"; } function deconstructTransfer(thing) { return transferable_1.isTransferDescriptor(thing) ? { payload: thing.send, transferables: thing.transferables } : { payload: thing, transferables: undefined }; } function postFunctionInitMessage() { const initMessage = { type: messages_1.WorkerMessageType.init, exposed: { type: "function" } }; implementation_1.default.postMessageToMaster(initMessage); } function postModuleInitMessage(methodNames) { const initMessage = { type: messages_1.WorkerMessageType.init, exposed: { type: "module", methods: methodNames } }; implementation_1.default.postMessageToMaster(initMessage); } function postJobErrorMessage(uid, rawError) { const { payload: error, transferables } = deconstructTransfer(rawError); const errorMessage = { type: messages_1.WorkerMessageType.error, uid, error: common_1.serialize(error) }; implementation_1.default.postMessageToMaster(errorMessage, transferables); } function postJobResultMessage(uid, completed, resultValue) { const { payload, transferables } = deconstructTransfer(resultValue); const resultMessage = { type: messages_1.WorkerMessageType.result, uid, complete: completed ? true : undefined, payload }; implementation_1.default.postMessageToMaster(resultMessage, transferables); } function postJobStartMessage(uid, resultType) { const startMessage = { type: messages_1.WorkerMessageType.running, uid, resultType }; implementation_1.default.postMessageToMaster(startMessage); } function postUncaughtErrorMessage(error) { try { const errorMessage = { type: messages_1.WorkerMessageType.uncaughtError, error: common_1.serialize(error) }; implementation_1.default.postMessageToMaster(errorMessage); } catch (subError) { // tslint:disable-next-line no-console console.error("Not reporting uncaught error back to master thread as it " + "occured while reporting an uncaught error already." + "\nLatest error:", subError, "\nOriginal error:", error); } } function runFunction(jobUID, fn, args) { return __awaiter(this, void 0, void 0, function* () { let syncResult; try { syncResult = fn(...args); } catch (error) { return postJobErrorMessage(jobUID, error); } const resultType = isObservable(syncResult) ? "observable" : "promise"; postJobStartMessage(jobUID, resultType); if (isObservable(syncResult)) { const subscription = syncResult.subscribe(value => postJobResultMessage(jobUID, false, common_1.serialize(value)), error => { postJobErrorMessage(jobUID, common_1.serialize(error)); activeSubscriptions.delete(jobUID); }, () => { postJobResultMessage(jobUID, true); activeSubscriptions.delete(jobUID); }); activeSubscriptions.set(jobUID, subscription); } else { try { const result = yield syncResult; postJobResultMessage(jobUID, true, common_1.serialize(result)); } catch (error) { postJobErrorMessage(jobUID, common_1.serialize(error)); } } }); } /** * Expose a function or a module (an object whose values are functions) * to the main thread. Must be called exactly once in every worker thread * to signal its API to the main thread. * * @param exposed Function or object whose values are functions */ function expose(exposed) { if (!implementation_1.default.isWorkerRuntime()) { throw Error("expose() called in the master thread."); } if (exposeCalled) { throw Error("expose() called more than once. This is not possible. Pass an object to expose() if you want to expose multiple functions."); } exposeCalled = true; if (typeof exposed === "function") { implementation_1.default.subscribeToMasterMessages(messageData => { if (isMasterJobRunMessage(messageData) && !messageData.method) { runFunction(messageData.uid, exposed, messageData.args.map(common_1.deserialize)); } }); postFunctionInitMessage(); } else if (typeof exposed === "object" && exposed) { implementation_1.default.subscribeToMasterMessages(messageData => { if (isMasterJobRunMessage(messageData) && messageData.method) { runFunction(messageData.uid, exposed[messageData.method], messageData.args.map(common_1.deserialize)); } }); const methodNames = Object.keys(exposed).filter(key => typeof exposed[key] === "function"); postModuleInitMessage(methodNames); } else { throw Error(`Invalid argument passed to expose(). Expected a function or an object, got: ${exposed}`); } implementation_1.default.subscribeToMasterMessages(messageData => { if (isMasterJobCancelMessage(messageData)) { const jobUID = messageData.uid; const subscription = activeSubscriptions.get(jobUID); if (subscription) { subscription.unsubscribe(); activeSubscriptions.delete(jobUID); } } }); } exports.expose = expose; if (typeof self !== "undefined" && typeof self.addEventListener === "function" && implementation_1.default.isWorkerRuntime()) { self.addEventListener("error", event => { // Post with some delay, so the master had some time to subscribe to messages setTimeout(() => postUncaughtErrorMessage(event.error || event), 250); }); self.addEventListener("unhandledrejection", event => { const error = event.reason; if (error && typeof error.message === "string") { // Post with some delay, so the master had some time to subscribe to messages setTimeout(() => postUncaughtErrorMessage(error), 250); } }); } if (typeof process !== "undefined" && typeof process.on === "function" && implementation_1.default.isWorkerRuntime()) { process.on("uncaughtException", (error) => { // Post with some delay, so the master had some time to subscribe to messages setTimeout(() => postUncaughtErrorMessage(error), 250); }); process.on("unhandledRejection", (error) => { if (error && typeof error.message === "string") { // Post with some delay, so the master had some time to subscribe to messages setTimeout(() => postUncaughtErrorMessage(error), 250); } }); }