Error Handling with try/catch
In Promise chains, errors propagate through .catch. In async/await code, the equivalent is try/catch — the same syntax you would use for synchronous code.
Basic try/catch in an async function
Section titled “Basic try/catch in an async function”async function fetchRates() { try { const response = await fetch('https://open.er-api.com/v6/latest/USD'); if (!response.ok) throw new Error(`HTTP error ${response.status}`); const data = await response.json(); if (data.result !== 'success') throw new Error(`API error: ${data['error-type']}`); return data.rates; } catch (error) { console.error('Failed to fetch rates:', error.message); return { USD: 1 }; // graceful fallback }}If any await rejects, or if any throw fires, execution jumps to the catch block — just like synchronous error handling.
The catch block receives the error
Section titled “The catch block receives the error”error in the catch block is the rejection reason — whatever was thrown or rejected with. It is usually an Error object:
try { const data = await fetchRates();} catch (error) { console.error(error.message); // human-readable message console.error(error.stack); // full stack trace for debugging}finally in async/await
Section titled “finally in async/await”finally works the same as in Promise chains — runs after the try/catch regardless of outcome:
async function loadApp() { showSpinner(); try { const rates = await fetchRates(); renderExpenses(expenses, rates); } catch (error) { showError('Could not load exchange rates.'); } finally { hideSpinner(); // runs whether it succeeded or failed }}Granular error handling
Section titled “Granular error handling”Use multiple try/catch blocks when different errors need different responses:
async function initBudgetBuddy() { let rates;
try { rates = await fetchRates(); } catch { // exchange rates optional — degrade gracefully rates = { USD: 1 }; }
try { const storedExpenses = loadExpenses(); renderExpenses(storedExpenses, rates); } catch (error) { console.error('Failed to load expenses:', error); renderExpenses([], rates); // render empty state }}Each try/catch handles one concern. A failure in one does not crash the other.
Unhandled rejections
Section titled “Unhandled rejections”If an async function throws and the caller does not handle it, you get an unhandled Promise rejection — a warning in development and sometimes a crash in production.
Always either await inside a try/catch, or chain .catch() on the returned Promise at the call site:
// Option 1: try/catch insideasync function safeInit() { try { await initBudgetBuddy(); } catch (error) { console.error('Init failed:', error); }}safeInit();
// Option 2: .catch on the call siteinitBudgetBuddy().catch(error => console.error('Init failed:', error));Both are valid. Option 2 is more concise when you just need to log the error without additional recovery.
Re-throwing errors
Section titled “Re-throwing errors”Sometimes you want to catch an error, do something specific (log it, transform it), then let it propagate:
async function fetchRates() { try { const response = await fetch('https://open.er-api.com/v6/latest/USD'); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); return data.rates; } catch (error) { // Add context before re-throwing throw new Error(`fetchRates failed: ${error.message}`); }}The caller receives a more informative error without knowing the internal details of how fetching works.
Exercise
Section titled “Exercise”- Rewrite the
fetchRates()function from Module 05 using async/await and try/catch instead of a Promise chain. - Add a
finallyblock that logs'fetchRates complete'regardless of outcome. - Write an
initBudgetBuddy()function with two separate try/catch blocks — one for fetching rates, one for loading expenses — so a failure in one does not affect the other. - Deliberately call
fetchRates()without await or.catch— confirm the unhandled rejection warning appears in the console.
try/catchin async functions catches both thrown errors and rejected awaited Promises.catch (error)receives the rejection reason — anErrorobject withmessageandstack.finallyruns after the try/catch regardless of outcome.- Use multiple try/catch blocks when different errors need different recovery strategies.
- Always handle errors from async functions — either with try/catch or by chaining
.catchat the call site.