UNPKG

@ribajs/shopify

Version:

Shopify extension for Riba.js

285 lines (237 loc) 6.94 kB
// https://github.com/sindresorhus/p-queue import { PriorityQueue } from "./priority-queue.service.js"; export class PQueue { public queue = new PriorityQueue(); private _carryoverConcurrencyCount: number; private _isIntervalIgnored: boolean; private _intervalCount = 0; private _intervalCap: typeof Infinity; private _interval: number; private _intervalId?: any; private _intervalEnd = 0; private _timeoutId: any | null; private _pendingCount = 0; private _concurrency: typeof Infinity; private _isPaused: boolean; private _resolveEmpty: () => any; private _resolveIdle: () => any; constructor(options: any) { options = Object.assign( { carryoverConcurrencyCount: false, intervalCap: Infinity, interval: 0, concurrency: Infinity, autoStart: true, queueClass: PriorityQueue, }, options, ); if ( !(typeof options.concurrency === "number" && options.concurrency >= 1) ) { throw new TypeError( `Expected \`concurrency\` to be a number from 1 and up, got \`${ options.concurrency }\` (${typeof options.concurrency})`, ); } if ( !(typeof options.intervalCap === "number" && options.intervalCap >= 1) ) { throw new TypeError( `Expected \`intervalCap\` to be a number from 1 and up, got \`${ options.intervalCap }\` (${typeof options.intervalCap})`, ); } if ( !( typeof options.interval === "number" && Number.isFinite(options.interval) && options.interval >= 0 ) ) { throw new TypeError( `Expected \`interval\` to be a finite number >= 0, got \`${ options.interval }\` (${typeof options.interval})`, ); } this._carryoverConcurrencyCount = options.carryoverConcurrencyCount; this._isIntervalIgnored = options.intervalCap === Infinity || options.interval === 0; this._intervalCount = 0; this._intervalCap = options.intervalCap; this._interval = options.interval; this._intervalId = undefined; this._intervalEnd = 0; this._timeoutId = null; this.queue = new options.queueClass(); this._pendingCount = 0; this._concurrency = options.concurrency; this._isPaused = options.autoStart === false; this._resolveEmpty = () => {}; this._resolveIdle = () => {}; } public add(fn: any, options?: any): Promise<any> { return new Promise((resolve, reject) => { const run = () => { this._pendingCount++; this._intervalCount++; try { Promise.resolve(fn()).then( (val) => { resolve(val); this._next(); }, (err) => { reject(err); this._next(); }, ); } catch (err) { reject(err); this._next(); } }; this.queue.enqueue(run, options); this._tryToStartAnother(); }); } public addAll(fns: any, options: any) { return Promise.all(fns.map((fn: any) => this.add(fn, options))); } public start() { if (!this._isPaused) { return; } this._isPaused = false; while (this._tryToStartAnother()) {} } public pause() { this._isPaused = true; } public clear() { this.queue = new PriorityQueue(); } public onEmpty() { // Instantly resolve if the queue is empty if (this.queue.size === 0) { return Promise.resolve(); } return new Promise<void>((resolve) => { const existingResolve = this._resolveEmpty; this._resolveEmpty = () => { existingResolve(); resolve(); }; }); } public onIdle(): Promise<void> { // Instantly resolve if none pending and if nothing else is queued if (this._pendingCount === 0 && this.queue.size === 0) { return Promise.resolve(); } return new Promise((resolve) => { const existingResolve = this._resolveIdle; this._resolveIdle = () => { existingResolve(); resolve(); }; }); } // private _resolveEmpty = () => { }; // eslint-disable-line // private _resolveIdle = () => { }; // eslint-disable-line get _doesIntervalAllowAnother() { return this._isIntervalIgnored || this._intervalCount < this._intervalCap; } get _doesConcurrentAllowAnother() { return this._pendingCount < this._concurrency; } private _next() { this._pendingCount--; this._tryToStartAnother(); } private _resolvePromises() { this._resolveEmpty(); this._resolveEmpty = () => {}; if (this._pendingCount === 0) { this._resolveIdle(); this._resolveIdle = () => {}; } } private _onResumeInterval() { this._onInterval(); this._initializeIntervalIfNeeded(); this._timeoutId = null; } private _intervalPaused() { const now = Date.now(); if (typeof this._intervalId === undefined) { const delay = this._intervalEnd - now; if (delay < 0) { // Act as the interval was done // We don't need to resume it here, // because it'll be resumed on line 160 this._intervalCount = this._carryoverConcurrencyCount ? this._pendingCount : 0; } else { // Act as the interval is pending if (this._timeoutId === null) { this._timeoutId = setTimeout(() => this._onResumeInterval(), delay); } return true; } } return false; } private _tryToStartAnother() { if (this.queue.size === 0) { // We can clear the interval ("pause") // because we can redo it later ("resume") clearInterval(this._intervalId); this._intervalId = undefined; this._resolvePromises(); return false; } if (!this._isPaused) { const canInitializeInterval = !this._intervalPaused(); if (this._doesIntervalAllowAnother && this._doesConcurrentAllowAnother) { this.queue.dequeue()(); if (canInitializeInterval) { this._initializeIntervalIfNeeded(); } return true; } } return false; } private _initializeIntervalIfNeeded() { if (this._isIntervalIgnored || this._intervalId !== null) { return; } this._intervalId = setInterval(() => this._onInterval(), this._interval); this._intervalEnd = Date.now() + this._interval; } private _onInterval() { if (this._intervalCount === 0 && this._pendingCount === 0) { clearInterval(this._intervalId); this._intervalId = undefined; } this._intervalCount = this._carryoverConcurrencyCount ? this._pendingCount : 0; while (this._tryToStartAnother()) {} } get size() { return this.queue.size; } get pending() { return this._pendingCount; } get isPaused() { return this._isPaused; } }