Skip to content

Instantly share code, notes, and snippets.

@anechunaev
Created April 18, 2023 11:20
Show Gist options
  • Save anechunaev/4c7addd966095afdf75b5a2953e6b697 to your computer and use it in GitHub Desktop.
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.
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