ts-batch-fetch
Version:
A concurrency-controlled fetch library for browsers using ts-batch-processor
100 lines (99 loc) • 3.16 kB
JavaScript
import GlobalConfig from "./config";
/**
* Global store for managing fetch concurrency across the entire application
*/
export default class GlobalFetchStore {
constructor() {
this._activeRequests = 0;
this._requestQueue = [];
}
static get instance() {
if (!GlobalFetchStore._instance) {
GlobalFetchStore._instance = new GlobalFetchStore();
}
return GlobalFetchStore._instance;
}
get activeRequests() {
return this._activeRequests;
}
get requestQueue() {
return [...this._requestQueue];
}
get config() {
return GlobalConfig.instance.config;
}
/**
* Execute a fetch request with concurrency management
*/
async executeFetch(resource, init) {
// If we're at the concurrency limit, queue this request
if (this._activeRequests >= this.config.concurrency) {
return new Promise((resolve, reject) => {
this._requestQueue.push(async () => {
try {
const result = await this._executeFetchInternal(resource, init);
resolve(result);
}
catch (error) {
reject(error);
}
});
});
}
// Otherwise execute immediately
return this._executeFetchInternal(resource, init);
}
/**
* Internal method to execute fetch and manage active request count
*/
async _executeFetchInternal(resource, init) {
// Create timeout controller if timeout is specified
const timeoutMs = init?.timeout || GlobalConfig.instance.timeout;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
// Merge default init with provided init
const finalInit = {
...GlobalConfig.instance.defaultInit,
...init,
signal: controller.signal,
};
// Execute the fetch
this._activeRequests++;
const response = await fetch(resource, finalInit);
clearTimeout(timeoutId);
return response;
}
catch (error) {
clearTimeout(timeoutId);
throw error;
}
finally {
this._activeRequests--;
// Process next queued request if any
this._processNextQueuedRequest();
}
}
/**
* Process the next queued request if concurrency allows
*/
_processNextQueuedRequest() {
if (this._requestQueue.length > 0 &&
this._activeRequests < this.config.concurrency) {
const nextRequest = this._requestQueue.shift();
if (nextRequest) {
nextRequest();
}
}
}
/**
* Get current status of the store
*/
getStatus() {
return {
activeRequests: this._activeRequests,
queueLength: this._requestQueue.length,
config: this.config,
};
}
}