UNPKG

mypqueue

Version:

My Implementation of a Concurrent Promise Queue

107 lines (106 loc) 4.29 kB
// refactor of https://github.com/doo-gl/concurrent-promise-queue/blob/main/src/index.ts // better/more explicit encapsulation // using maps for promisesBeingExecuted and promiseExecutedCallbacks // try catch, error handling improvements, and add my logger with winston // spliting up execute() into multiple functions import { v4 as uuid } from "uuid"; import { createLogger, transports, format } from "winston"; export class MyConcurrentPromiseQueue { maxNumberOfConcurrentPromises; unitOfTimeMillis; maxThroughputPerUnitTime; promisesToExecute = []; promisesBeingExecuted = new Map(); promiseExecutedCallbacks = new Map(); promiseCompletedTimesLog = []; logger_; constructor(options) { const defaultOptions = { maxNumberOfConcurrentPromises: 1, unitOfTimeMillis: 100, maxThroughputPerUnitTime: 1000, }; const finalOptions = { ...defaultOptions, ...options }; this.maxNumberOfConcurrentPromises = finalOptions.maxNumberOfConcurrentPromises; this.unitOfTimeMillis = finalOptions.unitOfTimeMillis; this.maxThroughputPerUnitTime = finalOptions.maxThroughputPerUnitTime; this.logger_ = createLogger({ transports: [new transports.Console()], format: format.combine(format.colorize(), format.timestamp(), format.printf(({ timestamp, level, message }) => { return `[${timestamp}] ${level}: ${message}`; })), }); } numberOfQueuedPromises() { return this.promisesToExecute.length; } numberOfExecutingPromises() { return this.promisesBeingExecuted.size; } addPromise(promiseSupplier) { return new Promise((resolve, reject) => { const id = uuid(); this.promisesToExecute.push({ id, promiseSupplier }); this.promiseExecutedCallbacks.set(id, ({ isSuccess, result, error }) => { isSuccess ? resolve(result) : reject(error); }); this.execute(); }); } execute() { try { while (this.canExecuteMorePromises()) { const promise = this.promisesToExecute.shift(); if (!promise) return; this.promisesBeingExecuted.set(promise.id, promise); promise .promiseSupplier() .then((result) => { this.onPromiseFulfilled(promise.id, result); this.logger_.info(`Promise ${promise.id} fulfilled`); this.logger_.info(`${promise.id} result, ${result}`); }) .catch((error) => { this.onPromiseRejected(promise.id, error); this.logger_.error(`Promise ${promise.id} failed`); this.logger_.error(`${promise.id} error, ${error}`); }); } } catch (error) { this.logger_.error(`execute error, ${error}`); } } canExecuteMorePromises() { const now = Date.now(); const timeThreshold = now - this.unitOfTimeMillis; this.promiseCompletedTimesLog = this.promiseCompletedTimesLog.filter((time) => time.getTime() >= timeThreshold); return (this.promisesBeingExecuted.size < this.maxNumberOfConcurrentPromises && this.promiseCompletedTimesLog.length < this.maxThroughputPerUnitTime); } onPromiseFulfilled(id, result) { const callback = this.promiseExecutedCallbacks.get(id); if (callback) { callback({ isSuccess: true, result, error: null }); } this.finalizePromise(id); } onPromiseRejected(id, error) { const callback = this.promiseExecutedCallbacks.get(id); if (callback) { callback({ isSuccess: false, result: null, error }); } this.finalizePromise(id); } finalizePromise(id) { this.promisesBeingExecuted.delete(id); this.promiseExecutedCallbacks.delete(id); this.promiseCompletedTimesLog.push(new Date(Date.now())); this.execute(); } turnOffLogger() { this.logger_.transports.forEach((t) => (t.silent = true)); } }