import { Item } from './Item' ; import { Priority } from './Priority' ; import type { Config } from './Config' ; export class PriorityQueue { private readonly config : Config = { comparator : ( a , b ) : Priority => { if ( a . priority === b . priority ) { return a . timestamp - b . timestamp ; } return b . priority - a . priority ; } , maxRetries : 0 , maxRetryDelay : Number . MAX_SAFE_INTEGER , retryDelay : 1000 , retryGrowthFactor : 1.3 , } ; private isRunning : boolean = false ; private queue : Item [ ] = [ ] ; constructor ( config ? ) { this . config = { ... this . config , ... config } ; } public add < T > ( thunkedPromise , priority = Priority . MEDIUM , label ? ) { return new Promise ( ( resolve , reject ) => { const item = new Item ( ) ; item . fn = thunkedPromise ; item . label = label ; item . priority = priority ; item . reject = reject ; item . resolve = resolve ; item . retry = 0 ; item . timestamp = Date . now ( ) + this . size ; this . enqueue ( item ) ; if ( ! this . isRunning ) { this . isRunning = true ; void this . processList ( ) ; } } ) ; } enqueue ( item ) { this . queue . push ( item ) ; this . queue . sort ( this . config . comparator ) ; } public delete ( label ) { this . queue = this . queue . filter ( item => item . label !== label ) ; } public deleteAll ( ) { this . queue = [ ] ; } public get all ( ) : Item [ ] { return this . queue ; } public get first ( ) : Item { return this . queue [ 0 ] ; } public get last ( ) : Item { return this . queue [ this . queue . length - 1 ] ; } public get size ( ) : number { return this . queue . length ; } private async processList ( ) { const item = this . queue . shift ( ) ; if ( ! item ) { this . isRunning = false ; return ; } try { item . resolve ( await item . fn ( ) ) ; void this . processList ( ) ; } catch ( error ) { if ( item . retry >= this . config . maxRetries ) { item . reject ( error ) ; void this . processList ( ) ; } else { this . enqueue ( item ) ; setTimeout ( ( ) => this . processList ( ) , this . getGrowingDelay ( item . retry ) ) ; item . retry ++ ; } } } private getGrowingDelay ( currentRetry ) { const delay = currentRetry < 1 ? this . config . retryDelay : this . config . retryDelay * currentRetry * this . config . retryGrowthFactor ; return Math . min ( delay , this . config . maxRetryDelay ) ; } public toString ( ) { return this . queue . map ( ( item , index ) => { return `"${ index }": ${ item . toString ( ) }` ; } ) . join ( '\r\n' ) ; } }