threads
Version:
Web workers & worker threads as simple as a function call
209 lines (208 loc) • 9.22 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.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);
}
});
}
;