Files
p2ns/includes/infrastructure/async_errors.js
T
2025-12-17 20:05:50 -05:00

120 lines
3.5 KiB
JavaScript

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<Promise>} promises - Array of promises
* @param {string} context - Context for error logging
* @returns {Promise<Array>} - 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
};