/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ // @flow export type Message = { data: { id: string, method: string, args: Array } }; function WorkerDispatcher() { this.msgId = 1; this.worker = null; } WorkerDispatcher.prototype = { start(url) { this.worker = new Worker(url); this.worker.onerror = () => { console.error(`Error in worker ${url}`); }; }, stop() { if (!this.worker) { return; } this.worker.terminate(); this.worker = null; }, task(method, { queue = false } = {}) { const calls = []; const push = (args: Array) => { return new Promise((resolve, reject) => { if (queue && calls.length === 0) { Promise.resolve().then(flush); } calls.push([args, resolve, reject]); if (!queue) { flush(); } }); }; const flush = () => { const items = calls.slice(); calls.length = 0; if (!this.worker) { return; } const id = this.msgId++; this.worker.postMessage({ id, method, calls: items.map(item => item[0]) }); const listener = ({ data: result }) => { if (result.id !== id) { return; } if (!this.worker) { return; } this.worker.removeEventListener("message", listener); result.results.forEach((resultData, i) => { const [, resolve, reject] = items[i]; if (resultData.error) { reject(resultData.error); } else { resolve(resultData.response); } }); }; this.worker.addEventListener("message", listener); }; return (...args: any) => push(args); } }; function workerHandler(publicInterface: Object) { return function(msg: Message) { const { id, method, calls } = (msg.data: any); Promise.all( calls.map(args => { try { const response = publicInterface[method].apply(undefined, args); if (response instanceof Promise) { return response.then( val => ({ response: val }), // Error can't be sent via postMessage, so be sure to // convert to string. err => ({ error: err.toString() }) ); } return { response }; } catch (error) { // Error can't be sent via postMessage, so be sure to convert to // string. return { error: error.toString() }; } }) ).then(results => { self.postMessage({ id, results }); }); }; } function streamingWorkerHandler( publicInterface: Object, { timeout = 100 }: Object = {}, worker: Object = self ) { async function streamingWorker(id, tasks) { let isWorking = true; const timeoutId = setTimeout(() => { isWorking = false; }, timeout); const results = []; while (tasks.length !== 0 && isWorking) { const { callback, context, args } = tasks.shift(); const result = await callback.call(context, args); results.push(result); } worker.postMessage({ id, status: "pending", data: results }); clearTimeout(timeoutId); if (tasks.length !== 0) { await streamingWorker(id, tasks); } } return async function(msg: Message) { const { id, method, args } = msg.data; const workerMethod = publicInterface[method]; if (!workerMethod) { console.error(`Could not find ${method} defined in worker.`); } worker.postMessage({ id, status: "start" }); try { const tasks = workerMethod(args); await streamingWorker(id, tasks); worker.postMessage({ id, status: "done" }); } catch (error) { worker.postMessage({ id, status: "error", error }); } }; } module.exports = { WorkerDispatcher, workerHandler, streamingWorkerHandler };