UNPKG

concurrent-worker

Version:
265 lines (260 loc) 9.92 kB
var noop = function () { return []; }; /** * Calling functions through strings makes sure that aggressive minification * does not break the call. The same applies to the apply call, this ensures no polyfill * is assumed for a rest spread which would also break the worker script. * * Tested with Webpack dev and prod mode, Closure Compiler ADVANCED mode with ES3, ES5 and ES6 target. * @param message */ var onmessage = function (message) { return new Promise(function (resolve) { resolve(self["run"].apply(null, message.data[1])); }) .then(function (result) { var transferrable = self["getTransferrables"](result); postMessage([message.data[0], result, false], transferrable); }) .catch(function (e) { var error = self["getError"](e); postMessage([message.data[0], error, true]); }) .finally(function () { if (self["terminateOnCompletion"]) { close(); } }); }; var getError = function (e) { if (typeof e === "string") { return e; } if (typeof e === "object") { var props = Object.getOwnPropertyNames(e); return props.reduce(function (acc, prop) { acc[prop] = e[prop]; return acc; }, {}); } return "Unknown error in Worker"; }; var getContextDeclaration = function (contextItem) { switch (typeof contextItem) { case "boolean": case "number": return contextItem; case "string": return "'" + contextItem + "'"; case "function": return "" + contextItem; case "object": return JSON.stringify(contextItem, null, 2); } }; var getContextString = function (context) { return context ? Object.keys(context) .map(function (key) { return "this." + key + " = " + getContextDeclaration(context[key]); }) .join(";\r\n\r\n") : ""; }; var isUrlRelative = function (url) { return url.indexOf("://") === -1 && url.indexOf("//") === -1; }; var getScriptImport = function (scripts, rootUrl) { if (rootUrl === void 0) { rootUrl = ""; } return scripts != null && scripts.length > 0 ? "importScripts(" + scripts .map(function (script) { return isUrlRelative(script) ? "\"" + rootUrl + script + "\"" : "\"" + script + "\""; }) .join(",") + ");" : ""; }; var getScript = function (execute, config, terminateOnCompletion) { var _a; return ("\n" + getScriptImport(config.scripts, config.rootUrl) + "\n" + getContextString(config.context) + "\nself.terminateOnCompletion = " + terminateOnCompletion + ";\nself.rootUrl = '" + config.rootUrl + "';\nself.getError = " + getError + ";\nself.getTransferrables = " + ((_a = config.outTransferable) !== null && _a !== void 0 ? _a : noop) + ";\nself.run = " + execute + ";\nself.onmessage = " + onmessage + ";\n").trim(); }; var createWorkerUrl = function (execute, config, terminateOnCompletion) { if (terminateOnCompletion === void 0) { terminateOnCompletion = false; } var script = getScript(execute, config, terminateOnCompletion); var blob = new Blob([script], { type: "application/javascript" }); return URL.createObjectURL(blob); }; /** * Create a worker onmessage callback that resolves or rejects if the sync id matches. */ var createWorkerCallback = function (worker, syncIdPing, resolve, reject) { return function cb(message) { var syncIdPong = message.data[0]; var dataOrError = message.data[1]; var hasError = message.data[2]; if (syncIdPing === syncIdPong) { if (hasError) { reject(dataOrError); } else { resolve(dataOrError); } worker.removeEventListener("message", cb); } }; }; /** * Call worker with given arguments, returns a promise that resolves when onmessage is called * with matching syncId. */ var executePromiseWorker = function (worker, syncId, args, transferrable) { if (transferrable === void 0) { transferrable = []; } return new Promise(function (resolve, reject) { worker.addEventListener("message", createWorkerCallback(worker, syncId, resolve, reject)); worker.postMessage([syncId, args], transferrable); }); }; /** * Creates a task that can be run in a webworker. If you want to use functions and variables from * the outer scope you must pass them in via the context parameter, else they will not be available. * This creation method creates a new web worker for each call to it allowing multiple calls to run in paralel. * * @param task Function to execute off the main thread * @param config Worker configuration */ var concurrent = function (task, config) { var _a; if (config === void 0) { config = {}; } var url = typeof task === "string" ? task : createWorkerUrl(task, config, true); var getTransferable = (_a = config.inTransferable) !== null && _a !== void 0 ? _a : noop; var run = (function (args) { var worker = new Worker(url); var transferable = getTransferable(args); return executePromiseWorker(worker, -1, args, transferable); }); var kill = function () { URL.revokeObjectURL(url); }; /** * Returns an identical copy that runs on it's own workers */ var clone = function () { return concurrent(url, config); }; return { clone: clone, kill: kill, run: run, }; }; /** * Creates a task that can be run in a webworker. If you want to use functions and variables from * the outer scope you must pass them in via the context parameter, else they will not be available. * This creation method uses a single web worker for all calls to it, calls will be processed synchronously * in that worker. Has les overhead than `create` but does not run multiple calls in paralel. * * @param task Function to execute off the main thread, or object url pointing to worker script * @param config Worker configuration */ var serial = function (task, config) { var _a; if (config === void 0) { config = {}; } var url = typeof task === "string" ? task : createWorkerUrl(task, config); var worker = new Worker(url); var getTransferable = (_a = config.inTransferable) !== null && _a !== void 0 ? _a : noop; var syncId = 0; var run = (function (args) { var transferable = getTransferable(args); return executePromiseWorker(worker, syncId++, args, transferable); }); var kill = function () { worker.terminate(); URL.revokeObjectURL(url); }; /** * Returns an identical copy that runs on it's own worker */ var clone = function () { return serial(url, config); }; return { clone: clone, kill: kill, run: run, }; }; var _a, _b; var defaultConcurrency = (_b = (_a = self === null || self === void 0 ? void 0 : self.navigator) === null || _a === void 0 ? void 0 : _a.hardwareConcurrency) !== null && _b !== void 0 ? _b : 4; var defaultTimeout = 1000 * 60; var WorkerPool = /** @class */ (function () { function WorkerPool(task, config) { if (config === void 0) { config = {}; } var _a; this.task = task; this.config = config; this.workers = []; this.busy = []; this.waiting = []; var url = typeof task === "string" ? task : createWorkerUrl(task, config); var workers = (_a = config === null || config === void 0 ? void 0 : config.workers) !== null && _a !== void 0 ? _a : defaultConcurrency; for (var i = 0; i < workers; i++) { this.workers.push(serial(url, config)); } } WorkerPool.prototype.get = function () { var _this = this; var worker = this.workers.pop(); if (worker != null) { this.busy.push(worker); return Promise.resolve(worker); } return new Promise(function (resolve, reject) { var _a, _b; var timeout = setTimeout(function () { var index = _this.waiting.indexOf(cb); if (index !== -1) { _this.waiting.splice(index, 1); } reject("No workers available, timeout of " + timeout + "ms exceeded."); }, (_b = (_a = _this.config) === null || _a === void 0 ? void 0 : _a.timeout) !== null && _b !== void 0 ? _b : defaultTimeout); var cb = function (w) { clearTimeout(timeout); resolve(w); }; _this.waiting.unshift(cb); }); }; WorkerPool.prototype.release = function (worker) { var next = this.waiting.pop(); if (next) { next(worker); } else { var index = this.busy.indexOf(worker); if (index !== -1) { this.busy.splice(index, 1); } this.workers.push(worker); } }; WorkerPool.prototype.kill = function () { this.workers.forEach(function (worker) { return worker.kill(); }); this.busy.forEach(function (worker) { return worker.kill(); }); }; return WorkerPool; }()); var pool = function (task, config) { if (config === void 0) { config = {}; } var workerPool = new WorkerPool(task, config); var run = (function (args) { return workerPool.get().then(function (worker) { return worker.run(args).then(function (res) { workerPool.release(worker); return res; }); }); }); var kill = function () { workerPool.kill(); }; var clone = function () { return pool(task, config); }; return { clone: clone, kill: kill, run: run, }; }; export { concurrent, pool, serial };