Created
April 18, 2023 11:20
-
-
Save anechunaev/4c7addd966095afdf75b5a2953e6b697 to your computer and use it in GitHub Desktop.
This class helps to work with queue of async tasks (e g network requests) with some restrictions on how many tasks could be running at the same time. By default it will store new tasks to internal queue and run no more than 5 tasks with 1 retry.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type TFuture<T> = () => Promise<T> | |
export type TQueueOptions<T> = { | |
maxActiveRequests?: number | |
dataHandler?: (data: T) => void | |
errorHandler?: (error: Error) => void | |
retries?: number | |
doneCallback?: (error?: Error, data?: Array<Error | T | undefined>) => void | |
failOnError?: boolean | |
} | |
type TRequest<T, U> = { | |
id: string | |
request: TFuture<U> | |
retries: number | |
data?: T | |
error?: Error | |
} | |
export class RequestQueue<T> { | |
private requestMap: Map<string, TRequest<T, AxiosResponse<T>>> = new Map() | |
private queue: string[] = [] | |
private currentActiveRequests = 0 | |
private maxActiveRequests: number | |
private dataHandler: (data: T) => void | |
private errorHandler: (error: Error) => void | |
private retries: number | |
private doneCallback: ( | |
error?: Error, | |
data?: Array<Error | T | undefined> | |
) => void | |
private failOnError: boolean | |
constructor(options: TQueueOptions<T>) { | |
this.maxActiveRequests = | |
typeof options.maxActiveRequests === 'number' | |
? options.maxActiveRequests | |
: 5 | |
this.dataHandler = | |
typeof options.dataHandler === 'function' ? options.dataHandler : () => {} | |
this.errorHandler = | |
typeof options.errorHandler === 'function' | |
? options.errorHandler | |
: () => {} | |
this.retries = typeof options.retries === 'number' ? options.retries : 1 | |
this.doneCallback = | |
typeof options.doneCallback === 'function' | |
? options.doneCallback | |
: () => {} | |
this.failOnError = | |
typeof options.failOnError === 'boolean' ? options.failOnError : false | |
} | |
public add(request: TFuture<AxiosResponse<T>>) { | |
const req = this.createRequest(request) | |
this.requestMap.set(req.id, req) | |
this.queue.push(req.id) | |
if (this.currentActiveRequests < this.maxActiveRequests) { | |
this.currentActiveRequests++ | |
this.run() | |
} | |
} | |
public getResults(): Array<Error | T | undefined> { | |
return Array.from(this.requestMap.values()).map(r => r.error || r.data) | |
} | |
private run() { | |
const requestId = this.queue.pop() | |
const req = this.requestMap.get(requestId || '') | |
if (!req) return this.doneCallback(undefined, this.getResults()) | |
req | |
.request() | |
.then(response => { | |
this.dataHandler(response.data) | |
req.data = response.data | |
}) | |
.catch(requestError => { | |
if (req.retries < this.retries) { | |
req.retries++ | |
this.queue.push(req.id) | |
} else { | |
this.errorHandler(requestError) | |
req.error = requestError | |
} | |
}) | |
.finally(() => { | |
if (this.failOnError && req.error) { | |
this.doneCallback(req.error, this.getResults()) | |
} else { | |
this.run() | |
} | |
}) | |
} | |
private createRequest( | |
request: TFuture<AxiosResponse<T>> | |
): TRequest<T, AxiosResponse<T>> { | |
return { | |
id: this.createRequestId(), | |
request, | |
retries: 0, | |
} | |
} | |
private createRequestId(): string { | |
let id = Math.floor(Math.random() * 1000000).toString(16) | |
if (this.requestMap.has(id)) { | |
id = this.createRequestId() | |
} | |
return id | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment