JavaScript의 비밀스러운 삶: 거절

발행: (2026년 2월 20일 오후 05:42 GMT+9)
3 분 소요
원문: Dev.to

Source: Dev.to

Why async errors bypass try/catch

Timothy placed a try/catch block at the top of his application, confident that any error would be caught:

function loadDashboard() {
    try {
        // Initiating a background network request
        fetch('/api/corrupted-data');
        console.log("Dashboard loading...");
    } catch (error) {
        console.error("Safe Landing:", error.message);
    }
}

loadDashboard();

The console printed Dashboard loading…. Two seconds later the process crashed with:

UnhandledPromiseRejection: Failed to fetch

What happened?

  1. fetch() returns a pending Promise immediately; it does not wait for the network request.
  2. The try block finishes successfully, so the call stack is unwound and the catch block is discarded.
  3. When the Promise later rejects, there is no longer a stack frame for the original try/catch to handle the error. The rejection becomes an unhandled promise rejection, which modern browsers and Node.js treat as a fatal error.

Attaching the error handler to the Promise

The fix is to attach a handler directly to the Promise:

function loadDashboard() {
    fetch('/api/corrupted-data')
        .then(data => console.log("Data loaded!"))
        .catch(error => console.error("Safe Landing:", error.message));

    console.log("Dashboard loading...");
}
  • The catch method creates an error boundary on the Promise itself, so when the network request fails, the rejection is routed to that handler instead of floating away.

Using async/await with try/catch

If you prefer the try/catch syntax, await pauses the function execution and preserves the error boundary:

async function loadDashboard() {
    try {
        console.log("Dashboard loading..."); // moved before the await

        // Execution is suspended here; the try/catch stays in memory.
        await fetch('/api/corrupted-data');

        console.log("Data loaded successfully!");
    } catch (error) {
        console.error("Safe Landing:", error.message);
    }
}
  • When await encounters a rejected Promise, the engine resumes the function, restores the try/catch context, and throws the error, which is then caught by the surrounding catch block.

Senior Tip: Callbacks and Event Listeners

Callbacks (e.g., setTimeout, DOM event listeners) run in a brand‑new call stack. Wrapping the registration code in a try/catch does not protect the callback body.

// ❌ WRONG: The try/catch runs during setup, not when the event fires.
try {
    button.addEventListener('click', () => {
        throw new Error("Click failed!"); // crashes the app
    });
} catch (e) {
    // This block is long gone by the time the click occurs.
}
// ✅ RIGHT: Place the try/catch **inside** the callback.
button.addEventListener('click', () => {
    try {
        throw new Error("Click failed!"); // caught safely
    } catch (e) {
        console.error("Safe Landing:", e.message);
    }
});
0 조회
Back to Blog

관련 글

더 보기 »