mypqueue
Version:
My Implementation of a Concurrent Promise Queue
107 lines (106 loc) • 4.29 kB
JavaScript
// 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));
}
}