docs: remove Phase 2 completion and executive summary files
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Retry utilities with exponential backoff
|
||||
*
|
||||
* Features:
|
||||
* - Simple retry with fixed delay
|
||||
* - Exponential backoff with jitter
|
||||
* - Callback hooks for logging/monitoring
|
||||
*/
|
||||
|
||||
const sleep = (ms) => new Promise(res => setTimeout(res, ms));
|
||||
|
||||
/**
|
||||
* Simple retry with fixed delay
|
||||
*
|
||||
* @param {Function} fn - Async function to retry
|
||||
* @param {Object} options
|
||||
* @param {number} options.retries - Number of retries (default: 3)
|
||||
* @param {number} options.delayMs - Fixed delay between retries (default: 100)
|
||||
* @param {Function} options.onRetry - Callback on retry attempt
|
||||
* @returns {Promise} Result of fn
|
||||
*/
|
||||
export async function retry(fn, { retries = 3, delayMs = 100, onRetry } = {}) {
|
||||
let lastErr;
|
||||
|
||||
for (let i = 0; i <= retries; i++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
lastErr = err;
|
||||
|
||||
if (i === retries) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (onRetry) {
|
||||
onRetry({ attempt: i + 1, maxAttempts: retries + 1, err, delay: delayMs });
|
||||
}
|
||||
|
||||
await sleep(delayMs);
|
||||
}
|
||||
}
|
||||
|
||||
throw lastErr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry with exponential backoff and jitter
|
||||
*
|
||||
* @param {Function} fn - Async function to retry
|
||||
* @param {Object} options
|
||||
* @param {number} options.retries - Number of retries (default: 3)
|
||||
* @param {number} options.baseDelayMs - Base delay for exponential backoff (default: 100)
|
||||
* @param {number} options.maxDelayMs - Maximum delay cap (default: 2000)
|
||||
* @param {boolean} options.jitter - Add random jitter (default: true)
|
||||
* @param {Function} options.onRetry - Callback on retry attempt
|
||||
* @returns {Promise} Result of fn
|
||||
*/
|
||||
export async function retryWithBackoff(fn, {
|
||||
retries = 3,
|
||||
baseDelayMs = 100,
|
||||
maxDelayMs = 2000,
|
||||
jitter = true,
|
||||
onRetry
|
||||
} = {}) {
|
||||
let lastErr;
|
||||
|
||||
for (let i = 0; i <= retries; i++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
lastErr = err;
|
||||
|
||||
if (i === retries) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Exponential backoff: baseDelay * 2^attempt
|
||||
const exponential = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, i));
|
||||
|
||||
// Add jitter: random value between 50% and 100% of exponential
|
||||
const delay = jitter
|
||||
? Math.floor(exponential * (0.5 + Math.random() * 0.5))
|
||||
: exponential;
|
||||
|
||||
if (onRetry) {
|
||||
onRetry({
|
||||
attempt: i + 1,
|
||||
maxAttempts: retries + 1,
|
||||
delay,
|
||||
err,
|
||||
exponential
|
||||
});
|
||||
}
|
||||
|
||||
await sleep(delay);
|
||||
}
|
||||
}
|
||||
|
||||
throw lastErr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry with circuit breaker pattern
|
||||
* Fails fast after consecutive errors threshold
|
||||
*
|
||||
* @param {Function} fn - Async function to retry
|
||||
* @param {Object} options
|
||||
* @param {number} options.retries - Retries per attempt (default: 2)
|
||||
* @param {number} options.failureThreshold - Failures before circuit opens (default: 5)
|
||||
* @param {number} options.resetTimeoutMs - Time before circuit half-open (default: 30000)
|
||||
* @param {Function} options.onRetry - Callback on retry
|
||||
* @param {Function} options.onCircuitOpen - Callback when circuit opens
|
||||
* @returns {Promise} Result of fn
|
||||
*/
|
||||
export class CircuitBreaker {
|
||||
constructor({
|
||||
retries = 2,
|
||||
failureThreshold = 5,
|
||||
resetTimeoutMs = 30_000
|
||||
} = {}) {
|
||||
this.retries = retries;
|
||||
this.failureThreshold = failureThreshold;
|
||||
this.resetTimeoutMs = resetTimeoutMs;
|
||||
this.failureCount = 0;
|
||||
this.state = 'closed'; // 'closed' | 'open' | 'half-open'
|
||||
this.lastFailureTime = null;
|
||||
}
|
||||
|
||||
async execute(fn, { onRetry, onCircuitOpen } = {}) {
|
||||
// Check if circuit should reset
|
||||
if (this.state === 'open') {
|
||||
const timeSinceFailure = Date.now() - this.lastFailureTime;
|
||||
if (timeSinceFailure >= this.resetTimeoutMs) {
|
||||
this.state = 'half-open';
|
||||
this.failureCount = 0;
|
||||
} else {
|
||||
throw new Error(`Circuit breaker is open (reset in ${this.resetTimeoutMs - timeSinceFailure}ms)`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await retryWithBackoff(fn, { retries: this.retries, onRetry });
|
||||
|
||||
// Success: reset failure count
|
||||
if (this.state === 'half-open') {
|
||||
this.state = 'closed';
|
||||
}
|
||||
this.failureCount = 0;
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
this.failureCount++;
|
||||
this.lastFailureTime = Date.now();
|
||||
|
||||
if (this.failureCount >= this.failureThreshold) {
|
||||
this.state = 'open';
|
||||
if (onCircuitOpen) {
|
||||
onCircuitOpen({ failureCount: this.failureCount });
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.state = 'closed';
|
||||
this.failureCount = 0;
|
||||
this.lastFailureTime = null;
|
||||
}
|
||||
|
||||
getState() {
|
||||
return {
|
||||
state: this.state,
|
||||
failureCount: this.failureCount,
|
||||
failureThreshold: this.failureThreshold
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user