UNPKG

@focus-reactive/graphql-content-layer

Version:

[![npm version](https://badge.fury.io/js/%40focus-reactive%2Fgraphql-content-layer.svg)](https://badge.fury.io/js/%40focus-reactive%2Fgraphql-content-layer) # GitNation GraphQL Content Layer

107 lines (93 loc) 2.91 kB
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;