Asher Cohen
Back to posts

Asynchronous Error Handling in JavaScript

Handle async errors with promises, async/await, and modern error handling patterns

Introduction

Asynchronous error handling differs fundamentally from synchronous error handling. Traditional try/catch doesn't work with async operations like timers, events, or promises.

Why Async Errors Are Different

Async operations execute on different tracks than synchronous code:

// ❌ This won't work
try {
  setTimeout(() => {
    throw new Error('Something went wrong!');
  }, 1000);
} catch (error) {
  // Never reaches here - try/catch is long gone
}

Error Handling Patterns

Timers

Handle errors inside the callback:

setTimeout(() => {
  try {
    riskyOperation();
  } catch (error) {
    console.error('Timer error:', error);
  }
}, 1000);

Event Handlers

button.addEventListener('click', () => {
  try {
    doSomething();
  } catch (error) {
    console.error('Event error:', error);
  }
});

Promises

Use .catch():

fetch('/api/data')
  .then((res) => res.json())
  .catch((error) => console.error(error));

Async/Await

Use try/catch:

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    return await response.json();
  } catch (error) {
    console.error('Fetch failed:', error);
    throw error;
  }
}

Global Error Handlers

Window.onerror

window.onerror = (message, source, lineno, colno, error) => {
  console.error('Global error:', error);
  logToService(error);
  return true;
};

Unhandled Promise Rejections

window.onunhandledrejection = (event) => {
  console.error('Unhandled rejection:', event.reason);
  event.preventDefault();
};

Best Practices

Always Handle Promise Rejections

// ✅ Always add catch
fetch('/api/data')
  .then((res) => res.json())
  .catch((error) => console.error(error));

Use Finally for Cleanup

async function loadData() {
  try {
    return await fetchData();
  } catch (error) {
    console.error('Load failed:', error);
  } finally {
    hideLoadingSpinner(); // Always runs
  }
}

Provide Context

async function fetchUser(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    return await response.json();
  } catch (error) {
    throw new Error(`Failed to fetch user ${userId}: ${error.message}`);
  }
}

Common Pitfalls

Forgetting .catch()

// ❌ Unhandled rejection
fetch('/api/data').then((res) => res.json());

// ✅ Handle errors
fetch('/api/data')
  .then((res) => res.json())
  .catch((error) => console.error(error));

Swallowing Errors

// ❌ Silent failure
fetch('/api/data').catch(() => {});

// ✅ Log errors
fetch('/api/data').catch((error) => {
  console.error('Fetch failed:', error);
});

Conclusion

Use .catch() for promises, try/catch with async/await, and global handlers as safety nets. Always handle errors gracefully.

#javascript #error-handling #async #promises