Async Error Handling in Frontend JavaScript
Errors from asynchronous work such as promises, timers, and event callbacks generally require explicit handling at the async operation itself. React-style error boundaries do not replace `try/catch`, promise rejection handling, or callback-level error handling.
Synchronous exceptions can often be caught by a surrounding `try/catch`, but asynchronous execution changes when and where an error occurs. A `try/catch` around code that merely starts asynchronous work does not automatically catch an exception thrown later inside a timer or promise callback.
Promises represent success or failure through fulfillment and rejection. Handle failures with `.catch()` or with `await` inside a `try/catch`. If a promise can reject and no code handles the rejection, the application may produce an unhandled rejection warning or error depending on the runtime.
Callbacks such as `setTimeout` and event handlers also need local error handling when they can throw. For timer callbacks, the callback runs after the original call stack has completed, so an outer `try/catch` around `setTimeout(...)` cannot catch an exception thrown inside the callback.
In React applications, error boundaries are designed primarily for errors during rendering and certain component lifecycle work. They do not generally catch failures from asynchronous callbacks, promise rejections, or event handlers, so those paths need their own handling strategy.
Key points
- Handle promise failures with `.catch()` or `try/catch` around `await`.
- An outer `try/catch` does not catch exceptions thrown later by `setTimeout` or other callbacks.
- A rejected promise should have an intentional handling path.
- React error boundaries do not replace explicit handling for async work or event-handler failures.
Interview questions
What is the difference between throwing an error and rejecting a promise?
Throwing is the synchronous exception mechanism, while rejection is the failure state of a promise. Inside an `async` function, throwing an exception causes the returned promise to reject. A rejected promise can then be handled with `.catch()` or by awaiting it inside `try/catch`. The two mechanisms are closely connected but operate through different control-flow models.
Why doesn't a `try/catch` around `setTimeout` catch an error thrown inside the callback?
`setTimeout` schedules the callback and returns before that callback executes. The `try` block has already finished when the callback eventually runs. Therefore, an exception inside the callback occurs on a later turn of execution and is outside the original `try/catch`. The callback itself needs appropriate error handling.
How do you handle errors from an async function?
An `async` function returns a promise, and failures normally appear as a rejected promise. When using `await`, put the operation in a `try/catch` if the caller needs to recover or report the failure. Alternatively, the caller can attach `.catch()` to the returned promise. The important part is that the rejection has an intentional handling path.
Do React error boundaries catch asynchronous errors?
Not generally. Error boundaries are intended for certain errors that occur during React rendering and related component lifecycle processing. They do not serve as a general-purpose handler for promise rejections, timer callbacks, or event-handler exceptions. Those asynchronous paths should use explicit error handling appropriate to the operation.