On this page
The core idea
An error thrown inside an async function becomes a rejected promise. If nothing awaits that promise inside a try/catch, or attaches a .catch handler, the error does not stop your program in an obvious place; it surfaces later as an unhandled rejection or disappears entirely. Good async error handling is mostly about ownership: for each operation, one layer decides how to recover, what to tell the user and what to record for debugging. Lower layers should add context and rethrow rather than hide the failure.
A practical approach
Step 1
Always await or return promises you start. A fire-and-forget call such as saveDraft() without await loses its errors unless you deliberately attach a handler.
Step 2
Catch errors at the boundary that can act on them: the UI event handler that can show a message, the request handler that can return an HTTP status, or the job runner that can retry.
Step 3
When catching only to add context, rethrow with the original error attached: throw new Error('Could not load course 42', { cause: error }).
Step 4
Distinguish expected failures, such as a 404 or validation error, from unexpected ones. Show helpful messages for the first and log full details for the second.
Step 5
Add a global safety net with window.addEventListener('unhandledrejection', ...) in browsers or process.on('unhandledRejection', ...) in Node.js, and treat every report as a bug to fix at its source.
Worked example
A registration form calls fetch to submit a learner's details. The code checks only for network errors, so when the server responds with 422 Unprocessable Entity, fetch resolves normally and the form shows 'Registered!'. Adding if (!response.ok) throw new RegistrationError(await response.json()) turns server rejections into real errors. The submit handler catches RegistrationError and shows the field messages, while any other error is logged with the request ID and the learner sees a general retry message.
A mistake to avoid
Avoid empty catch blocks and catch(e) { console.log(e) } in production code. Both make the program continue as if nothing happened, often leaving data half saved and the user unaware that their action failed.
Try it yourself
Find three places in a project where a promise is started without await or a handler. For each, decide which layer owns the failure, add the handling there and write a test that forces the error.
Your implementation checklist
- Every promise is awaited, returned or given a deliberate handler
- Errors are handled where the code can recover or inform the user
- Rethrown errors keep the original cause
- HTTP error statuses are converted into errors explicitly
- A global handler reports unhandled rejections
Share or cite this guide
Innovate with Ajay Editorial (2026-10-07). Handle Async Errors in JavaScript So Failures Reach Someone. https://innovatewithajay.com/blogs/handle-async-errors-in-javascript
