UNPKG

threads

Version:

Web workers & worker threads as simple as a function call

200 lines (199 loc) 8.33 kB
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()); }); }; import isSomeObservable from "is-observable"; import { deserialize, serialize } from "../common"; import { isTransferDescriptor } from "../transferable"; import { MasterMessageType, WorkerMessageType } from "../types/messages"; import Implementation from "./implementation"; export { registerSerializer } from "../common"; export { Transfer } from "../transferable"; /** Returns `true` if this code is currently running in a worker. */ export const isWorkerRuntime = Implementation.isWorkerRuntime; let exposeCalled = false; const activeSubscriptions = new Map(); const isMasterJobCancelMessage = (thing) => thing && thing.type === MasterMessageType.cancel; const isMasterJobRunMessage = (thing) => thing && thing.type === 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) => isSomeObservable(thing) || isZenObservable(thing); function isZenObservable(thing) { return thing && typeof thing === "object" && typeof thing.subscribe === "function"; } function deconstructTransfer(thing) { return isTransferDescriptor(thing) ? { payload: thing.send, transferables: thing.transferables } : { payload: thing, transferables: undefined }; } function postFunctionInitMessage() { const initMessage = { type: WorkerMessageType.init, exposed: { type: "function" } }; Implementation.postMessageToMaster(initMessage); } function postModuleInitMessage(methodNames) { const initMessage = { type: WorkerMessageType.init, exposed: { type: "module", methods: methodNames } }; Implementation.postMessageToMaster(initMessage); } function postJobErrorMessage(uid, rawError) { const { payload: error, transferables } = deconstructTransfer(rawError); const errorMessage = { type: WorkerMessageType.error, uid, error: serialize(error) }; Implementation.postMessageToMaster(errorMessage, transferables); } function postJobResultMessage(uid, completed, resultValue) { const { payload, transferables } = deconstructTransfer(resultValue); const resultMessage = { type: WorkerMessageType.result, uid, complete: completed ? true : undefined, payload }; Implementation.postMessageToMaster(resultMessage, transferables); } function postJobStartMessage(uid, resultType) { const startMessage = { type: WorkerMessageType.running, uid, resultType }; Implementation.postMessageToMaster(startMessage); } function postUncaughtErrorMessage(error) { try { const errorMessage = { type: WorkerMessageType.uncaughtError, error: serialize(error) }; Implementation.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, serialize(value)), error => { postJobErrorMessage(jobUID, serialize(error)); activeSubscriptions.delete(jobUID); }, () => { postJobResultMessage(jobUID, true); activeSubscriptions.delete(jobUID); }); activeSubscriptions.set(jobUID, subscription); } else { try { const result = yield syncResult; postJobResultMessage(jobUID, true, serialize(result)); } catch (error) { postJobErrorMessage(jobUID, 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 */ export function expose(exposed) { if (!Implementation.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.subscribeToMasterMessages(messageData => { if (isMasterJobRunMessage(messageData) && !messageData.method) { runFunction(messageData.uid, exposed, messageData.args.map(deserialize)); } }); postFunctionInitMessage(); } else if (typeof exposed === "object" && exposed) { Implementation.subscribeToMasterMessages(messageData => { if (isMasterJobRunMessage(messageData) && messageData.method) { runFunction(messageData.uid, exposed[messageData.method], messageData.args.map(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.subscribeToMasterMessages(messageData => { if (isMasterJobCancelMessage(messageData)) { const jobUID = messageData.uid; const subscription = activeSubscriptions.get(jobUID); if (subscription) { subscription.unsubscribe(); activeSubscriptions.delete(jobUID); } } }); } if (typeof self !== "undefined" && typeof self.addEventListener === "function" && Implementation.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.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); } }); }