Async/Await and Promise Errors - Complete Troubleshooting Guide
Learn to debug and fix common async/await and promise errors in JavaScript. Master error handling patterns for asynchronous code.
Async/Await and Promise Errors - Complete Troubleshooting Guide
Asynchronous JavaScript can be tricky, and errors in async code can be particularly challenging to debug. This guide covers the most common async/await and promise errors, their causes, and solutions.
1. UnhandledPromiseRejectionWarning #
This is one of the most common async errors you'll encounter.
❌ Problem #
// Unhandled promise rejection
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
// If fetch fails, no error handler!
// Or with async/await
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
fetchData(); // No error handling!
✅ Solution #
// With .catch()
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
// With async/await and try/catch
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
throw error; // Re-throw if needed
}
}
// Handle the async function call
fetchData()
.then(data => console.log(data))
.catch(error => console.error('Final error:', error));
2. SyntaxError: await is only valid in async functions #
This occurs when you try to use await
outside of an async function.
❌ Problem #
// Using await in non-async function
function fetchUser() {
const response = await fetch('/api/user'); // SyntaxError!
return response.json();
}
// Using await at top level (Node.js < 14.8)
const data = await fetch('/api/data'); // SyntaxError!
✅ Solution #
// Make the function async
async function fetchUser() {
const response = await fetch('/api/user');
return response.json();
}
// For top-level await, use an async IIFE
(async () => {
const data = await fetch('/api/data');
console.log(data);
})();
// Or in modern Node.js with ES modules
// (add "type": "module" to package.json)
const data = await fetch('/api/data');
3. TypeError: Cannot read property 'then' of undefined #
This happens when you try to call .then()
on something that's not a promise.
❌ Problem #
function getData() {
if (Math.random() > 0.5) {
return fetch('/api/data'); // Returns a promise
}
// Returns undefined!
}
getData().then(data => console.log(data)); // TypeError!
✅ Solution #
function getData() {
if (Math.random() > 0.5) {
return fetch('/api/data');
}
return Promise.resolve(null); // Always return a promise
}
// Or better - make it always async
async function getData() {
if (Math.random() > 0.5) {
const response = await fetch('/api/data');
return response.json();
}
return null;
}
4. Promise Chain Errors #
Common mistakes when chaining promises.
❌ Problem #
// Forgetting to return promises
fetch('/api/user')
.then(response => {
response.json(); // Missing return!
})
.then(data => {
console.log(data); // undefined!
});
// Not handling errors in the chain
fetch('/api/user')
.then(response => response.json())
.then(data => {
throw new Error('Something went wrong');
})
.then(result => {
console.log(result); // This won't run
});
// No .catch() handler!
✅ Solution #
// Always return promises
fetch('/api/user')
.then(response => {
return response.json(); // Return the promise
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
// Better with async/await
async function fetchUser() {
try {
const response = await fetch('/api/user');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
5. Async/Await in Loops #
Common mistakes when using async/await in loops.
❌ Problem #
// Sequential execution (slow)
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// forEach with async (doesn't work as expected)
async function processItemsWrong(items) {
const results = [];
items.forEach(async (item) => {
const result = await processItem(item);
results.push(result);
});
return results; // Returns empty array!
}
✅ Solution #
// Concurrent execution (fast)
async function processItems(items) {
const results = await Promise.all(
items.map(item => processItem(item))
);
return results;
}
// If you need sequential execution
async function processItemsSequential(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// For concurrent with limit
async function processItemsConcurrentLimit(items, limit = 3) {
const results = [];
for (let i = 0; i < items.length; i += limit) {
const batch = items.slice(i, i + limit);
const batchResults = await Promise.all(
batch.map(item => processItem(item))
);
results.push(...batchResults);
}
return results;
}
6. Fetch API Errors #
Common mistakes when using the Fetch API.
❌ Problem #
// Not checking if response is ok
async function fetchData() {
const response = await fetch('/api/data');
return response.json(); // Might fail if response is not ok
}
// Not handling network errors
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
// No error handling for network failures
✅ Solution #
// Always check response status
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error instanceof TypeError) {
console.error('Network error:', error.message);
} else {
console.error('HTTP error:', error.message);
}
throw error;
}
}
// Comprehensive error handling
async function fetchDataRobust(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('Response is not JSON');
}
return await response.json();
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
7. Race Conditions #
Async operations can create race conditions where the order of execution matters.
❌ Problem #
let data = null;
async function loadData() {
data = await fetch('/api/data').then(r => r.json());
}
async function useData() {
console.log(data); // Might be null if loadData hasn't finished
}
// Race condition
loadData();
useData();
✅ Solution #
// Use proper coordination
class DataManager {
constructor() {
this.data = null;
this.loading = null;
}
async loadData() {
if (this.loading) {
return this.loading; // Return existing promise
}
this.loading = fetch('/api/data')
.then(r => r.json())
.then(data => {
this.data = data;
return data;
});
return this.loading;
}
async getData() {
if (this.data) {
return this.data;
}
return await this.loadData();
}
}
const manager = new DataManager();
// Usage
async function useData() {
const data = await manager.getData();
console.log(data);
}
8. Memory Leaks with Promises #
Promises can create memory leaks if not handled properly.
❌ Problem #
// Creating promises that never resolve
function createHangingPromise() {
return new Promise((resolve, reject) => {
// Never call resolve or reject
setTimeout(() => {
// This creates a memory leak
}, 10000);
});
}
// Accumulating promises without cleanup
const promises = [];
setInterval(() => {
promises.push(fetch('/api/data'));
}, 1000);
✅ Solution #
// Always resolve or reject promises
function createTimeoutPromise(timeout) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
resolve('timeout');
}, timeout);
// Cleanup function
return () => {
clearTimeout(timeoutId);
};
});
}
// Use AbortController for fetch
const controller = new AbortController();
async function fetchWithTimeout(url, timeout = 5000) {
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was aborted');
}
throw error;
}
}
9. Error Propagation Issues #
Problems with how errors bubble up through async code.
❌ Problem #
// Swallowing errors
async function fetchUserData() {
try {
const response = await fetch('/api/user');
return response.json();
} catch (error) {
console.log('Error occurred'); // Logged but not re-thrown
return null; // Hides the error
}
}
// Caller doesn't know about the error
const userData = await fetchUserData();
console.log(userData); // Could be null
✅ Solution #
// Proper error propagation
async function fetchUserData() {
try {
const response = await fetch('/api/user');
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error fetching user data:', error);
throw error; // Re-throw to allow caller to handle
}
}
// Caller can handle the error appropriately
async function displayUser() {
try {
const userData = await fetchUserData();
console.log(userData);
} catch (error) {
console.error('Failed to display user:', error);
// Show user-friendly error message
showErrorMessage('Unable to load user data');
}
}
10. Testing Async Code Errors #
Common issues when testing async code.
❌ Problem #
// Test doesn't wait for async operation
test('should fetch data', () => {
fetchData().then(data => {
expect(data).toBeTruthy();
});
// Test completes before promise resolves
});
// Not testing error cases
test('should handle errors', async () => {
const data = await fetchData();
expect(data).toBeTruthy(); // Only tests success case
});
✅ Solution #
// Proper async testing
test('should fetch data', async () => {
const data = await fetchData();
expect(data).toBeTruthy();
});
// Test error cases
test('should handle fetch errors', async () => {
// Mock fetch to reject
global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
await expect(fetchData()).rejects.toThrow('Network error');
});
// Test both success and error cases
describe('fetchData', () => {
it('should return data on success', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ id: 1, name: 'Test' })
});
const data = await fetchData();
expect(data).toEqual({ id: 1, name: 'Test' });
});
it('should throw error on HTTP error', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: false,
status: 404,
statusText: 'Not Found'
});
await expect(fetchData()).rejects.toThrow('HTTP 404: Not Found');
});
it('should throw error on network error', async () => {
global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
await expect(fetchData()).rejects.toThrow('Network error');
});
});
Debugging Async Code #
Tools and Techniques #
// Add detailed logging
async function debugFetchData() {
console.log('Starting fetch...');
try {
const response = await fetch('/api/data');
console.log('Response received:', response.status, response.statusText);
const data = await response.json();
console.log('Data parsed:', data);
return data;
} catch (error) {
console.error('Error details:', {
name: error.name,
message: error.message,
stack: error.stack
});
throw error;
}
}
// Use performance timing
async function timedFetch(url) {
const start = performance.now();
try {
const response = await fetch(url);
const end = performance.now();
console.log(`Fetch took ${end - start} milliseconds`);
return response;
} catch (error) {
const end = performance.now();
console.log(`Failed fetch took ${end - start} milliseconds`);
throw error;
}
}
Best Practices for Error Handling #
- Always handle promise rejections
- Use try/catch with async/await
- Provide meaningful error messages
- Log errors for debugging
- Re-throw errors when appropriate
- Use specific error types
- Test both success and error cases
- Clean up resources in finally blocks
// Comprehensive error handling pattern
async function robustApiCall(url, options = {}) {
const { retries = 3, timeout = 5000 } = options;
for (let i = 0; i <= retries; i++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
if (i === retries) {
throw error;
}
if (error.name === 'AbortError') {
console.log(`Request timeout on attempt ${i + 1}`);
} else {
console.log(`Request failed on attempt ${i + 1}: ${error.message}`);
}
// Exponential backoff
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Summary #
Common async/await and promise errors include:
- Unhandled promise rejections - Always add error handlers
- Using await outside async functions - Make functions async
- Not returning promises - Always return in promise chains
- Incorrect loop usage - Use Promise.all() for concurrent operations
- Fetch API misuse - Always check response.ok
- Race conditions - Coordinate async operations properly
- Memory leaks - Clean up resources and use AbortController
- Error swallowing - Re-throw errors when appropriate
Remember: async code requires different debugging approaches. Use logging, testing, and proper error handling to build robust applications.
Next Steps #
Related Error Solutions
Are Java and Bedrock Seeds the Same? Common Confusion
Understand whether Java and Bedrock seeds are the same in Minecraft and how this relates to JavaScript development concepts.
Last updated: Jan 27, 2025
Are Java and JavaScript the Same? Common Confusion Explained
Are Java and JavaScript the same? Learn why this common confusion exists and discover the key differences between these two programming languages.
Last updated: Jan 27, 2025
Can JavaScript Be Used for Backend? Common Misconceptions
Address common myths about whether JavaScript can be used for backend development and explore server-side JavaScript capabilities.
Last updated: Jan 28, 2025
Common JavaScript Errors and How to Fix Them
Learn to identify and fix the most common JavaScript errors. From syntax errors to runtime exceptions, master debugging techniques with practical examples.