@focus-reactive/graphql-content-layer
Version:
[](https://badge.fury.io/js/%40focus-reactive%2Fgraphql-content-layer) # GitNation GraphQL Content Layer
107 lines (93 loc) • 2.91 kB
JavaScript
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
class RequestQueue {
constructor(options = {}) {
this.concurrency = options.concurrency || 5;
this.retryAttempts = options.retryAttempts || 3;
this.retryDelay = options.retryDelay || 1000;
this.maxRetryDelay = options.maxRetryDelay || 30000;
this.timeout = options.timeout || 60000;
this.queue = [];
this.running = 0;
this.results = new Map();
}
async add(fn, id) {
return new Promise((resolve, reject) => {
this.queue.push({
fn,
id,
resolve,
reject,
attempts: 0
});
this.process();
});
}
async process() {
while (this.running < this.concurrency && this.queue.length > 0) {
const task = this.queue.shift();
this.running++;
this.executeTask(task);
}
}
async executeTask(task) {
const {
fn,
id,
resolve,
reject,
attempts
} = task;
try {
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Request timeout after ${this.timeout}ms`)), this.timeout));
const result = await Promise.race([fn(), timeoutPromise]);
resolve(result);
if (id) {
this.results.set(id, {
success: true,
data: result
});
}
} catch (error) {
const isRetriableError = this.isRetriable(error);
const nextAttempt = attempts + 1;
if (isRetriableError && nextAttempt < this.retryAttempts) {
const delay = Math.min(this.retryDelay * Math.pow(2, attempts), this.maxRetryDelay);
console.warn(`Request failed (attempt ${nextAttempt}/${this.retryAttempts}), retrying in ${delay}ms...`, {
error: error.message || error,
id
});
await sleep(delay);
task.attempts = nextAttempt;
this.queue.unshift(task);
} else {
console.error(`Request failed after ${nextAttempt} attempts`, {
error: error.message || error,
id
});
if (id) {
this.results.set(id, {
success: false,
error
});
}
reject(error);
}
} finally {
this.running--;
this.process();
}
}
isRetriable(error) {
const errorMessage = error.message || '';
const errorCode = error.code || '';
const statusCode = error.response && error.response.status;
if (statusCode === 429) return true;
if (errorCode === 'ETIMEDOUT' || errorCode === 'ECONNRESET') return true;
if (errorMessage.includes('timeout') || errorMessage.includes('ETIMEDOUT') || errorMessage.includes('429') || errorMessage.includes('Too Many Requests') || errorMessage.includes('rate limit')) {
return true;
}
if (statusCode >= 500 && statusCode < 600) return true;
return false;
}
}
module.exports = RequestQueue;