UNPKG

storycrawler

Version:

Utilities to build Storybook crawling tools with Puppeteer

188 lines (187 loc) 5.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createExecutionService = exports.Queue = exports.runParallel = exports.sleep = void 0; /** * * Waits for given time * **/ async function sleep(time = 0) { await Promise.resolve(); if (time <= 0) return; return new Promise(res => setTimeout(() => res(), time)); } exports.sleep = sleep; /** * * Allocates and executes tasks in parallel * * @param tasks - Generator function which yields next task * @param workers - Processors which deal each task * @returns List of results of the all tasks * **/ async function runParallel(tasks, workers) { const results = []; const p = workers.length; if (!p) throw new Error('No workers'); const generator = tasks(); await Promise.all([...new Array(p).keys()].map(i => new Promise((res, rej) => { async function next() { const { done, value: task } = await generator.next(); if (done || !task) return res(); try { results.push(await task(workers[i])); return await next(); } catch (error) { rej(error); } } return next(); }))); return results; } exports.runParallel = runParallel; const cancelationToken = Symbol('cancel'); /** * * Represents list of tasks waiting * **/ class Queue { /** * * @param opt - See {@link QueueOptions} * **/ constructor({ initialRequests, createTask, allowEmpty }) { this.requestIdCounter = 0; this.tobeContinued = true; this.futureRequests = []; this.resolvers = []; this.requestingIds = new Set(); this.createDelegationTask = createTask; this.allowEmpty = !!allowEmpty; if (initialRequests) { for (const req of initialRequests) { this.push(req); } } } /** * * Add a new request to this queue * * @param req - Request object * **/ push(req) { if (this.resolvers.length) { const resolver = this.resolvers.shift(); resolver.resolve(req); } else { this.futureRequests.push(Promise.resolve(req)); } } /** * * Ends to execute * * **/ close() { this.tobeContinued = false; this.resolvers.forEach(({ cancel }) => cancel()); } /** * * Creates a task generator * * @returns Generator function * **/ async *tasks() { const controller = this.publishController(); while (this.tobeContinued && (this.allowEmpty || this.futureRequests.length || this.requestingIds.size)) { if (this.futureRequests.length === 0) { this.futureRequests.push(new Promise((resolve, reject) => { const cancel = () => reject(cancelationToken); this.resolvers.push({ resolve, cancel }); })); } const futureRequest = this.futureRequests.shift(); try { const req = await futureRequest; yield this.createTask(req, controller); } catch (reason) { if (reason !== cancelationToken) { throw reason; } } } } /** * * Create {@link QueueController} instance corresponding to this queue * * @returns A queue controller * **/ publishController() { return { push: this.push.bind(this), close: async () => { await Promise.resolve(); this.close(); }, }; } generateId() { return `request_${++this.requestIdCounter}`; } createTask(req, controller) { const delegate = this.createDelegationTask(req, controller); const rid = this.generateId(); this.requestingIds.add(rid); return async (worker) => { const result = await delegate(worker); this.requestingIds.delete(rid); if (!this.allowEmpty && this.requestingIds.size === 0 && this.futureRequests.length === 0) { this.close(); } return result; }; } } exports.Queue = Queue; /** * * Creates queue and executor from worker and initial request. * * @param workers - List of workers to perform tasks * @param initialRequests - Initial requests * @param createTask - Converts from a given request to a task performed by each worker * @param options - Option parameters * @returns {@link ExecutionService} instance * **/ function createExecutionService(workers, initialRequests, createTask, options = {}) { const queue = new Queue({ initialRequests, createTask, allowEmpty: !!options.allowEmpty, }); return { execute() { return runParallel(queue.tasks.bind(queue), workers); }, ...queue.publishController(), }; } exports.createExecutionService = createExecutionService;