const { logError } = require('./logger'); /** * Wraps an async function to ensure errors are caught and logged * @param {Function} fn - Async function to wrap * @param {string} context - Context name for logging * @returns {Function} - Wrapped function */ function wrapAsync(fn, context = 'Unknown') { return async (...args) => { try { return await fn(...args); } catch (err) { logError('AsyncError', `Unhandled error in ${context}: ${err.message}`); logError('AsyncError', err.stack); throw err; } }; } /** * Wraps an async callback to ensure errors are caught * @param {Function} fn - Async callback function * @param {string} context - Context name for logging * @returns {Function} - Wrapped callback */ function wrapAsyncCallback(fn, context = 'Unknown') { return async (...args) => { try { return await fn(...args); } catch (err) { logError('AsyncError', `Unhandled error in callback ${context}: ${err.message}`); logError('AsyncError', err.stack); } }; } /** * Creates a promise that never rejects (catches all errors) * @param {Promise} promise - Promise to wrap * @param {string} context - Context for error logging * @returns {Promise} - Promise that resolves with { success, result, error } */ async function safePromise(promise, context = 'Unknown') { try { const result = await promise; return { success: true, result }; } catch (err) { logError('AsyncError', `Error in safe promise ${context}: ${err.message}`); return { success: false, error: err }; } } /** * Executes multiple promises safely, collecting results and errors * @param {Array} promises - Array of promises * @param {string} context - Context for error logging * @returns {Promise} - Array of { success, result, error } objects */ async function safeAll(promises, context = 'Unknown') { const results = await Promise.allSettled(promises); return results.map((result, index) => { if (result.status === 'fulfilled') { return { success: true, result: result.value }; } else { logError('AsyncError', `Error in safeAll promise ${index} (${context}): ${result.reason?.message || result.reason}`); return { success: false, error: result.reason }; } }); } /** * Retries an async function with exponential backoff * @param {Function} fn - Async function to retry * @param {object} options - Retry options * @param {number} options.maxRetries - Maximum number of retries * @param {number} options.initialDelay - Initial delay in ms * @param {number} options.maxDelay - Maximum delay in ms * @param {Function} options.shouldRetry - Function to determine if error should be retried * @returns {Promise} - Result of function call */ async function retryWithBackoff(fn, options = {}) { const { maxRetries = 3, initialDelay = 1000, maxDelay = 10000, shouldRetry = () => true } = options; let lastError; let delay = initialDelay; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (err) { lastError = err; if (attempt === maxRetries || !shouldRetry(err)) { throw err; } logError('Retry', `Attempt ${attempt + 1}/${maxRetries + 1} failed: ${err.message}. Retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); delay = Math.min(delay * 2, maxDelay); } } throw lastError; } module.exports = { wrapAsync, wrapAsyncCallback, safePromise, safeAll, retryWithBackoff };