JavaScript Async/Await Error Prevention Utilities and Code Snippets
Ready-to-use utility functions and code snippets to prevent 'undefined is not a function' errors in JavaScript async/await operations.
JavaScript Async/Await Error Prevention Utilities
This collection of utility functions and code snippets helps prevent the common "undefined is not a function" error when working with JavaScript async/await patterns. Copy and use these utilities in your projects for more robust async code.
Core Utility Functions #
1. Safe Async Function Caller #
/**
* Safely calls an async function with error handling
* @param {Function} asyncFn - The async function to call
* @param {Array} args - Arguments to pass to the function
* @param {*} fallback - Fallback value if function fails
* @returns {Promise} Result or fallback value
*/
async function safeAsyncCall(asyncFn, args = [], fallback = null) {
try {
if (typeof asyncFn !== 'function') {
console.warn('safeAsyncCall: Expected a function, got:', typeof asyncFn);
return fallback;
}
return await asyncFn(...args);
} catch (error) {
console.error('safeAsyncCall failed:', error.message);
return fallback;
}
}
// Example usage
const testFunction = async (name) => `Hello, ${name}!`;
// Test with valid function
safeAsyncCall(testFunction, ['World']).then(console.log); // "Hello, World!"
// Test with invalid function
safeAsyncCall(undefined, ['World'], 'Default').then(console.log); // "Default"
2. Async Method Validator #
/**
* Validates and safely calls object methods in async context
* @param {Object} obj - The object containing the method
* @param {string} methodName - Name of the method to call
* @param {Array} args - Arguments for the method
* @param {*} fallback - Fallback value
* @returns {Promise} Method result or fallback
*/
async function safeMethodCall(obj, methodName, args = [], fallback = null) {
if (!obj || typeof obj !== 'object') {
console.warn('safeMethodCall: Invalid object provided');
return fallback;
}
if (!(methodName in obj)) {
console.warn(`safeMethodCall: Method '${methodName}' not found in object`);
return fallback;
}
if (typeof obj[methodName] !== 'function') {
console.warn(`safeMethodCall: '${methodName}' is not a function`);
return fallback;
}
try {
const result = obj[methodName](...args);
// Handle both sync and async methods
return result instanceof Promise ? await result : result;
} catch (error) {
console.error(`safeMethodCall: Error calling '${methodName}':`, error.message);
return fallback;
}
}
// Example usage
const userAPI = {
fetchUser: async (id) => ({ id, name: `User ${id}` }),
validateUser: (user) => user && user.id && user.name
};
async function demo() {
// Safe async method call
const user = await safeMethodCall(userAPI, 'fetchUser', [123], { error: 'User not found' });
console.log('User:', user);
// Safe sync method call
const isValid = await safeMethodCall(userAPI, 'validateUser', [user], false);
console.log('Is valid:', isValid);
// Safe call to non-existent method
const result = await safeMethodCall(userAPI, 'nonExistent', [], 'No method');
console.log('Non-existent result:', result);
}
demo();
3. Async Pipeline Utility #
/**
* Creates a safe async pipeline that prevents function errors
* @param {...Function} functions - Async functions to chain
* @returns {Function} Pipeline function
*/
function createAsyncPipeline(...functions) {
return async function(initialValue) {
let result = initialValue;
for (let i = 0; i < functions.length; i++) {
const fn = functions[i];
if (typeof fn !== 'function') {
console.warn(`Pipeline step ${i}: Expected function, got ${typeof fn}`);
continue;
}
try {
const stepResult = await fn(result);
result = stepResult !== undefined ? stepResult : result;
} catch (error) {
console.error(`Pipeline step ${i} failed:`, error.message);
// Continue with previous result
}
}
return result;
};
}
// Example usage
const step1 = async (data) => ({ ...data, step1: true });
const step2 = async (data) => ({ ...data, step2: true });
const step3 = async (data) => ({ ...data, step3: true });
const pipeline = createAsyncPipeline(step1, step2, step3);
pipeline({ initial: 'data' }).then(result => {
console.log('Pipeline result:', result);
});
4. Async Retry Utility #
/**
* Retries an async function with exponential backoff
* @param {Function} asyncFn - Function to retry
* @param {Object} options - Retry configuration
* @returns {Promise} Function result or error
*/
async function retryAsync(asyncFn, options = {}) {
const {
maxRetries = 3,
baseDelay = 1000,
maxDelay = 10000,
backoffFactor = 2,
onRetry = null
} = options;
if (typeof asyncFn !== 'function') {
throw new Error('retryAsync: First argument must be a function');
}
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await asyncFn();
} catch (error) {
lastError = error;
if (attempt === maxRetries) {
break;
}
const delay = Math.min(
baseDelay * Math.pow(backoffFactor, attempt),
maxDelay
);
if (onRetry) {
onRetry(error, attempt + 1, delay);
}
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// Example usage
const unreliableFunction = async () => {
if (Math.random() < 0.7) {
throw new Error('Random failure');
}
return { success: true, timestamp: Date.now() };
};
retryAsync(unreliableFunction, {
maxRetries: 3,
baseDelay: 500,
onRetry: (error, attempt, delay) => {
console.log(`Retry ${attempt} after ${delay}ms due to:`, error.message);
}
}).then(result => {
console.log('Success:', result);
}).catch(error => {
console.log('Final failure:', error.message);
});
5. Async Timeout Wrapper #
/**
* Wraps an async function with a timeout
* @param {Function} asyncFn - Function to wrap
* @param {number} timeoutMs - Timeout in milliseconds
* @param {string} timeoutMessage - Custom timeout message
* @returns {Promise} Function result or timeout error
*/
function withTimeout(asyncFn, timeoutMs = 5000, timeoutMessage = 'Operation timed out') {
if (typeof asyncFn !== 'function') {
return Promise.reject(new Error('withTimeout: Expected a function'));
}
return function(...args) {
return Promise.race([
asyncFn.apply(this, args),
new Promise((_, reject) => {
setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
})
]);
};
}
// Example usage
const slowFunction = async (delay) => {
await new Promise(resolve => setTimeout(resolve, delay));
return `Completed after ${delay}ms`;
};
const timedFunction = withTimeout(slowFunction, 2000, 'Function took too long');
// This will succeed
timedFunction(1000).then(console.log).catch(console.error);
// This will timeout
timedFunction(3000).then(console.log).catch(console.error);
Error-Safe Async Patterns #
6. Batch Async Processing #
/**
* Processes items in batches with error isolation
* @param {Array} items - Items to process
* @param {Function} processor - Async processing function
* @param {Object} options - Processing options
* @returns {Promise<Array>} Results array
*/
async function batchProcess(items, processor, options = {}) {
const {
batchSize = 5,
continueOnError = true,
errorValue = null
} = options;
if (!Array.isArray(items)) {
throw new Error('batchProcess: Items must be an array');
}
if (typeof processor !== 'function') {
throw new Error('batchProcess: Processor must be a function');
}
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchPromises = batch.map(async (item, index) => {
try {
return await processor(item, i + index);
} catch (error) {
console.error(`Batch processing error at index ${i + index}:`, error.message);
return continueOnError ? errorValue : Promise.reject(error);
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
// Example usage
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const processItem = async (item) => {
if (item === 5) throw new Error('Item 5 always fails');
return item * 2;
};
batchProcess(items, processItem, {
batchSize: 3,
continueOnError: true,
errorValue: -1
}).then(results => {
console.log('Batch results:', results);
});
7. Async Queue Manager #
/**
* Manages async operations in a queue with concurrency control
*/
class AsyncQueue {
constructor(concurrency = 1) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
/**
* Adds a task to the queue
* @param {Function} asyncFn - Async function to execute
* @param {Array} args - Arguments for the function
* @returns {Promise} Task result
*/
add(asyncFn, args = []) {
return new Promise((resolve, reject) => {
if (typeof asyncFn !== 'function') {
reject(new Error('AsyncQueue: Task must be a function'));
return;
}
this.queue.push({
asyncFn,
args,
resolve,
reject
});
this.process();
});
}
async process() {
if (this.running >= this.concurrency || this.queue.length === 0) {
return;
}
this.running++;
const { asyncFn, args, resolve, reject } = this.queue.shift();
try {
const result = await asyncFn(...args);
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process(); // Process next task
}
}
/**
* Get queue status
* @returns {Object} Queue status information
*/
getStatus() {
return {
running: this.running,
queued: this.queue.length,
concurrency: this.concurrency
};
}
}
// Example usage
const queue = new AsyncQueue(2); // Max 2 concurrent tasks
const createTask = (id, delay) => async () => {
console.log(`Task ${id} started`);
await new Promise(resolve => setTimeout(resolve, delay));
console.log(`Task ${id} completed`);
return `Result ${id}`;
};
// Add tasks to queue
Promise.all([
queue.add(createTask(1, 1000)),
queue.add(createTask(2, 1500)),
queue.add(createTask(3, 800)),
queue.add(createTask(4, 1200))
]).then(results => {
console.log('All tasks completed:', results);
});
// Check queue status
setInterval(() => {
const status = queue.getStatus();
if (status.running > 0 || status.queued > 0) {
console.log('Queue status:', status);
}
}, 500);
Usage Guidelines #
Quick Integration Checklist #
- Choose the right utility for your use case
- Test with edge cases (null, undefined, non-functions)
- Configure appropriate fallbacks for your application
- Add logging to monitor utility usage
- Consider performance impact for high-frequency operations
Best Practices #
- Always validate function types before calling
- Provide meaningful fallback values
- Log errors appropriately for debugging
- Use TypeScript when possible for compile-time checks
- Test utilities with various input types
These utilities provide a solid foundation for preventing "undefined is not a function" errors in async JavaScript code. Copy the snippets you need and customize them for your specific use cases.