UNPKG

web-worker-helper

Version:

Utilities for running tasks on worker threads

432 lines (416 loc) 19.7 kB
(function () { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ function __awaiter(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()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __values(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } function __asyncValues(o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; // From https://github.com/rauschma/async-iter-demo/tree/master/src under MIT license // http://2ality.com/2016/10/asynchronous-iteration.html /** * Async Queue * - AsyncIterable: An async iterator can be * - Values can be pushed onto the queue * @example * const asyncQueue = new AsyncQueue(); * setTimeout(() => asyncQueue.enqueue('tick'), 1000); * setTimeout(() => asyncQueue.enqueue(new Error('done')), 10000); * for await (const value of asyncQueue) { * console.log(value); // tick * } */ var AsyncQueue = /** @class */ (function () { function AsyncQueue() { this._values = []; // enqueues > dequeues this._settlers = []; // dequeues > enqueues this._closed = false; } /** Return an async iterator for this queue */ AsyncQueue.prototype[Symbol.asyncIterator] = function () { return this; }; /** Push a new value - the async iterator will yield a promise resolved to this value */ AsyncQueue.prototype.push = function (value) { return this.enqueue(value); }; /** * Push a new value - the async iterator will yield a promise resolved to this value * Add an error - the async iterator will yield a promise rejected with this value */ AsyncQueue.prototype.enqueue = function (value) { if (this._closed) { throw new Error('Closed'); } if (this._settlers.length > 0) { if (this._values.length > 0) { throw new Error('Illegal internal state'); } var settler = this._settlers.shift(); if (value instanceof Error) { settler.reject(value); } else { settler.resolve({ value: value }); } } else { this._values.push(value); } }; /** Indicate that we not waiting for more values - The async iterator will be done */ AsyncQueue.prototype.close = function () { while (this._settlers.length > 0) { var settler = this._settlers.shift(); settler.resolve({ done: true }); } this._closed = true; }; // ITERATOR IMPLEMENTATION /** @returns a Promise for an IteratorResult */ AsyncQueue.prototype.next = function () { var _this = this; // If values in queue, yield the first value if (this._values.length > 0) { var value = this._values.shift(); if (value instanceof Error) { return Promise.reject(value); } return Promise.resolve({ done: false, value: value }); } // If queue is closed, the iterator is done if (this._closed) { if (this._settlers.length > 0) { throw new Error('Illegal internal state'); } return Promise.resolve({ done: true, value: undefined }); } // Yield a promise that waits for new values to be enqueued return new Promise(function (resolve, reject) { _this._settlers.push({ resolve: resolve, reject: reject }); }); }; return AsyncQueue; }()); // NOTE - there is a copy of this function is both in core and loader-utils // core does not need all the utils in loader-utils, just this one. /** * Returns an array of Transferrable objects that can be used with postMessage * https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage * @param object data to be sent via postMessage * @param recursive - not for application use * @param transfers - not for application use * @returns a transfer list that can be passed to postMessage */ function getTransferList(object, recursive, transfers) { if (recursive === void 0) { recursive = true; } // Make sure that items in the transfer list is unique var transfersSet = transfers || new Set(); if (!object) ; else if (isTransferable(object)) { transfersSet.add(object); } else if (isTransferable(object.buffer)) { // Typed array transfersSet.add(object.buffer); } else if (ArrayBuffer.isView(object)) ; else if (recursive && typeof object === 'object') { for (var key in object) { // Avoid perf hit - only go one level deep getTransferList(object[key], recursive, transfersSet); } } // If transfers is defined, is internal recursive call // Otherwise it's called by the user return transfers === undefined ? Array.from(transfersSet) : []; } // https://developer.mozilla.org/en-US/docs/Web/API/Transferable function isTransferable(object) { if (!object) { return false; } if (object instanceof ArrayBuffer) { return true; } if (typeof MessagePort !== 'undefined' && object instanceof MessagePort) { return true; } if (typeof ImageBitmap !== 'undefined' && object instanceof ImageBitmap) { return true; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (typeof OffscreenCanvas !== 'undefined' && object instanceof OffscreenCanvas) { return true; } return false; } var onMessageWrapperMap = new Map(); /** * Type safe wrapper for worker code */ var WorkerBody = /** @class */ (function () { function WorkerBody() { } Object.defineProperty(WorkerBody, "onmessage", { /* * (type: WorkerMessageType, payload: WorkerMessagePayload) => any */ set: function (onMessage) { self.onmessage = function (message) { if (!isKnownMessage(message)) { return; } // Confusingly the message itself also has a 'type' field which is always set to 'message' var _a = message.data, type = _a.type, payload = _a.payload; onMessage(type, payload); }; }, enumerable: false, configurable: true }); WorkerBody.addEventListener = function (onMessage) { var onMessageWrapper = onMessageWrapperMap.get(onMessage); if (!onMessageWrapper) { onMessageWrapper = function (message) { if (!isKnownMessage(message)) { return; } // Confusingly the message itself also has a 'type' field which is always set to 'message' var _a = message.data, type = _a.type, payload = _a.payload; onMessage(type, payload); }; } self.addEventListener('message', onMessageWrapper); }; WorkerBody.removeEventListener = function (onMessage) { var onMessageWrapper = onMessageWrapperMap.get(onMessage); onMessageWrapperMap.delete(onMessage); self.removeEventListener('message', onMessageWrapper); }; /** * Send a message from a worker to creating thread (main thread) * 从 worker 线程发送消息到主线程 * @param type * @param payload */ WorkerBody.postMessage = function (type, payload) { if (self) { var data = { source: 'Worker thread', type: type, payload: payload }; var transferList = getTransferList(payload); // TODO: targetOrigin, transferList // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore self.postMessage(data, transferList); } }; return WorkerBody; }()); // Filter out noise messages sent to workers function isKnownMessage(message) { var type = message.type, data = message.data; return type === 'message' && data && typeof data.source === 'string' && data.source === 'Main thread'; } /** Counter for jobs */ var requestId = 0; // 异步队列 var inputBatches; var options; /** * Set up a WebWorkerGlobalScope to talk with the main thread */ function createWorker(process, processInBatches) { var _this = this; // 检查是否在 worker 线程中 if (typeof self === 'undefined') { return; } var context = { process: processOnMainThread, }; WorkerBody.onmessage = function (type, payload) { return __awaiter(_this, void 0, void 0, function () { var _a, result, resultIterator, _b, resultIterator_1, resultIterator_1_1, batch, e_1_1, error_1, message; var _c, e_1, _d, _e; return __generator(this, function (_f) { switch (_f.label) { case 0: _f.trys.push([0, 19, , 20]); _a = type; switch (_a) { case 'process': return [3 /*break*/, 1]; case 'process-in-batches': return [3 /*break*/, 3]; case 'input-batch': return [3 /*break*/, 16]; case 'input-done': return [3 /*break*/, 17]; } return [3 /*break*/, 18]; case 1: if (!process) { throw new Error('Worker does not support atomic processing'); } return [4 /*yield*/, process(payload.input, payload.options || {}, context)]; case 2: result = _f.sent(); WorkerBody.postMessage('done', { result: result }); return [3 /*break*/, 18]; case 3: if (!processInBatches) { throw new Error('Worker does not support batched processing'); } inputBatches = new AsyncQueue(); options = payload.options || {}; resultIterator = processInBatches(inputBatches, options, context); _f.label = 4; case 4: _f.trys.push([4, 9, 10, 15]); _b = true, resultIterator_1 = __asyncValues(resultIterator); _f.label = 5; case 5: return [4 /*yield*/, resultIterator_1.next()]; case 6: if (!(resultIterator_1_1 = _f.sent(), _c = resultIterator_1_1.done, !_c)) return [3 /*break*/, 8]; _e = resultIterator_1_1.value; _b = false; batch = _e; WorkerBody.postMessage('output-batch', { result: batch }); _f.label = 7; case 7: _b = true; return [3 /*break*/, 5]; case 8: return [3 /*break*/, 15]; case 9: e_1_1 = _f.sent(); e_1 = { error: e_1_1 }; return [3 /*break*/, 15]; case 10: _f.trys.push([10, , 13, 14]); if (!(!_b && !_c && (_d = resultIterator_1.return))) return [3 /*break*/, 12]; return [4 /*yield*/, _d.call(resultIterator_1)]; case 11: _f.sent(); _f.label = 12; case 12: return [3 /*break*/, 14]; case 13: if (e_1) throw e_1.error; return [7 /*endfinally*/]; case 14: return [7 /*endfinally*/]; case 15: WorkerBody.postMessage('done', {}); return [3 /*break*/, 18]; case 16: inputBatches.push(payload.input); return [3 /*break*/, 18]; case 17: inputBatches.close(); return [3 /*break*/, 18]; case 18: return [3 /*break*/, 20]; case 19: error_1 = _f.sent(); message = error_1 instanceof Error ? error_1.message : ''; WorkerBody.postMessage('error', { error: message }); return [3 /*break*/, 20]; case 20: return [2 /*return*/]; } }); }); }; } function processOnMainThread(arrayBuffer, options) { if (options === void 0) { options = {}; } return new Promise(function (resolve, reject) { var id = requestId++; /** */ var onMessage = function (type, payload) { if (payload.id !== id) { // not ours return; } switch (type) { case 'done': WorkerBody.removeEventListener(onMessage); resolve(payload.result); break; case 'error': WorkerBody.removeEventListener(onMessage); reject(payload.error); break; // ignore } }; WorkerBody.addEventListener(onMessage); // Ask the main thread to decode data var payload = { id: id, input: arrayBuffer, options: options }; WorkerBody.postMessage('process', payload); }); } createWorker(function (data, options) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, data]; }); }); }); })(); //# sourceMappingURL=null.worker.js.map