UNPKG

bowling-analysis-system

Version:

A comprehensive system for analyzing bowling techniques using video processing and metrics calculation

275 lines (233 loc) 8.69 kB
/** * Standard error recovery strategies * @module ErrorStrategies */ /** * Retry a failed operation with exponential backoff * @param {Error} error - Error to recover from * @param {Object} context - Recovery context * @param {Function} context.operation - Operation to retry * @param {Number} [context.maxRetries=3] - Maximum number of retries * @param {Number} [context.initialDelay=100] - Initial delay in ms * @param {Number} [context.maxDelay=5000] - Maximum delay in ms * @param {Function} [context.shouldRetry] - Function to determine if retry should be attempted * @returns {Promise<*>} - Result of operation */ async function retryWithBackoff(error, context) { const operation = context.operation; if (typeof operation !== 'function') { throw new Error('Retry strategy requires an operation function'); } const maxRetries = context.maxRetries || 3; const initialDelay = context.initialDelay || 100; const maxDelay = context.maxDelay || 5000; const shouldRetry = context.shouldRetry || (() => true); const logger = context.logger || console; let attempt = 1; let lastError = error; while (attempt <= maxRetries) { try { // Wait with exponential backoff const delay = Math.min(initialDelay * Math.pow(2, attempt - 1), maxDelay); await new Promise(resolve => setTimeout(resolve, delay)); // Try the operation again return await operation(); } catch (retryError) { lastError = retryError; // Check if we should continue retrying if (!shouldRetry(retryError, attempt)) { logger.info(`Retry aborted at attempt ${attempt}: ${retryError.message}`); break; } logger.info(`Retry attempt ${attempt} failed: ${retryError.message}`); attempt++; } } // If we get here, all retries failed logger.error(`All ${maxRetries} retry attempts failed`); throw lastError; } /** * Use a fallback value when an operation fails * @param {Error} error - Error to recover from * @param {Object} context - Recovery context * @param {*} context.fallbackValue - Fallback value to return * @returns {*} - Fallback value */ async function useFallbackValue(error, context) { if (!('fallbackValue' in context)) { throw new Error('Fallback strategy requires a fallbackValue'); } const logger = context.logger || console; logger.info(`Using fallback value due to error: ${error.message}`); return context.fallbackValue; } /** * Circuit breaker to prevent cascading failures * @param {Error} error - Error to recover from * @param {Object} context - Recovery context * @param {Object} context.circuitBreaker - Circuit breaker state object * @param {Function} context.operation - Operation to perform * @param {Function} [context.fallback] - Fallback function * @returns {Promise<*>} - Result of operation or fallback */ async function circuitBreaker(error, context) { const circuitState = context.circuitBreaker; if (!circuitState) { throw new Error('Circuit breaker strategy requires a circuit state object'); } const operation = context.operation; const fallback = context.fallback; const logger = context.logger || console; // Initialize circuit state if needed if (!circuitState.failures) { circuitState.failures = 0; circuitState.lastFailure = 0; circuitState.isOpen = false; circuitState.threshold = context.threshold || 5; circuitState.resetTimeout = context.resetTimeout || 30000; } // Check if circuit is open if (circuitState.isOpen) { const timeSinceLastFailure = Date.now() - circuitState.lastFailure; // Check if it's time to try again if (timeSinceLastFailure >= circuitState.resetTimeout) { logger.info('Circuit breaker: Testing if service is available'); circuitState.isOpen = false; circuitState.failures = 0; } else { logger.info('Circuit breaker: Circuit is open, using fallback'); if (fallback) { return await fallback(error); } throw new Error('Circuit is open and no fallback provided'); } } // Circuit is closed or half-open, try the operation try { return await operation(); } catch (operationError) { // Record failure circuitState.failures++; circuitState.lastFailure = Date.now(); // Check if we should open the circuit if (circuitState.failures >= circuitState.threshold) { logger.warn(`Circuit breaker: Opening circuit after ${circuitState.failures} failures`); circuitState.isOpen = true; } // Try fallback if available if (fallback) { logger.info('Circuit breaker: Using fallback after failure'); return await fallback(operationError); } throw operationError; } } /** * Queue the operation for later retry * @param {Error} error - Error to recover from * @param {Object} context - Recovery context * @param {Array} context.queue - Queue to add the operation to * @param {Function} context.operation - Operation to queue * @param {Object} [context.metadata={}] - Metadata to store with the operation * @returns {Promise<Object>} - Queue status */ async function queueForLater(error, context) { const queue = context.queue; const operation = context.operation; const metadata = context.metadata || {}; const logger = context.logger || console; if (!Array.isArray(queue)) { throw new Error('Queue strategy requires a queue array'); } if (typeof operation !== 'function') { throw new Error('Queue strategy requires an operation function'); } // Add to retry queue const queueItem = { operation, error, timestamp: Date.now(), metadata, retryCount: 0 }; queue.push(queueItem); logger.info(`Queued operation for later retry. Queue size: ${queue.length}`); return { queued: true, queueLength: queue.length, item: queueItem }; } /** * Rollback a transaction when an operation fails * @param {Error} error - Error to recover from * @param {Object} context - Recovery context * @param {Function} context.rollback - Rollback function * @returns {Promise<Object>} - Rollback result */ async function rollbackTransaction(error, context) { const rollback = context.rollback; const logger = context.logger || console; if (typeof rollback !== 'function') { throw new Error('Rollback strategy requires a rollback function'); } logger.info(`Rolling back transaction due to error: ${error.message}`); try { const result = await rollback(error); return { rolledBack: true, result }; } catch (rollbackError) { logger.error('Failed to rollback transaction:', rollbackError); throw new Error(`Rollback failed: ${rollbackError.message}. Original error: ${error.message}`); } } /** * Use a cached result when an operation fails * @param {Error} error - Error to recover from * @param {Object} context - Recovery context * @param {Object} context.cache - Cache object * @param {String} context.key - Cache key * @returns {Promise<*>} - Cached result */ async function useCache(error, context) { const cache = context.cache; const key = context.key; const logger = context.logger || console; if (!cache) { throw new Error('Cache strategy requires a cache object'); } if (!key) { throw new Error('Cache strategy requires a key'); } if (!cache.has(key)) { throw new Error(`No cached value available for key: ${key}`); } logger.info(`Using cached value for key: ${key}`); return cache.get(key); } module.exports = { retryWithBackoff, useFallbackValue, circuitBreaker, queueForLater, rollbackTransaction, useCache, /** * Register all strategies with an ErrorRegistry * @param {Object} registry - ErrorRegistry instance */ registerAll(registry) { if (!registry || typeof registry.registerStrategy !== 'function') { throw new Error('Invalid ErrorRegistry provided'); } registry.registerStrategy('retry', retryWithBackoff); registry.registerStrategy('fallback', useFallbackValue); registry.registerStrategy('circuitBreaker', circuitBreaker); registry.registerStrategy('queue', queueForLater); registry.registerStrategy('rollback', rollbackTransaction); registry.registerStrategy('cache', useCache); } };