Error Handling in Async Code in Modern JavaScript Asynchronous operations represent the most common source of runtime failures in web applications—from intermittent network drops to API rate limits and aborted timeouts. Understanding how errors propagate across Promises, async/await, and event loops is essential for production stability.
The cleanest and most readable pattern for handling asynchronous errors is wrapping await calls in standard try...catch blocks:
1 async function fetchUserDashboard ( userId ) {
2 try {
3 const response = await fetch ( ` /api/users/ ${ userId } ` ) ;
4
5 if ( ! response . ok ) {
6 throw new Error ( ` API returned HTTP status ${ response . status } ` ) ;
7 }
8
9 const userData = await response . json ( ) ;
10 return userData ;
11 } catch ( error ) {
12 console . error ( ` Dashboard fetch failed for user ${ userId } : ` , error . message ) ;
13 // Return fallback state or rethrow
14 return { error : true , message : 'Unable to load dashboard data' } ;
15 }
16 }
2. The Unhandled Promise Rejection Hazard If a Promise is rejected and has no .catch() handler and is not caught inside a try...catch , JavaScript triggers an UnhandledPromiseRejection :
1 Rejected Promise
2 │
3 Is there a handler?
4 ┌────────┴────────┐
5 YES NO
6 │ │
7 ▼ ▼
8 .catch() / UnhandledPromiseRejection
9 try...catch (Node.js process terminates /
10 Browser logs red console error)
Global Safety Nets Always register global handlers to capture any stray unhandled rejections:
1 // In Modern Browsers:
2 window . addEventListener ( 'unhandledrejection' , ( event ) => {
3 console . warn ( ` UNHANDLED PROMISE REJECTION: ${ event . reason } ` ) ;
4 // Log to error tracking service (e.g. Sentry)
5 event . preventDefault ( ) ; // Suppress default browser error log
6 } ) ;
7
8 // In Node.js:
9 process . on ( 'unhandledRejection' , ( reason , promise ) => {
10 console . error ( 'Unhandled Rejection at:' , promise , 'reason:' , reason ) ;
11 } ) ;
3. Go-Style Tuple Pattern for Clean Async Handling Some developers prefer avoiding nested try...catch blocks by wrapping async operations in a helper that returns a [data, error] tuple (inspired by Go):
1 async function to ( promise ) {
2 try {
3 const data = await promise ;
4 return [ data , null ] ;
5 } catch ( error ) {
6 return [ null , error ] ;
7 }
8 }
9
10 // Usage:
11 async function loadFeed ( ) {
12 const [ data , err ] = await to ( fetchFeedFromNetwork ( ) ) ;
13 if ( err ) {
14 console . error ( 'Failed to load feed:' , err ) ;
15 return ;
16 }
17 console . log ( 'Feed loaded:' , data ) ;
18 }
4. Error Handling with Promise.allSettled() When running multiple independent operations in parallel, Promise.all() fails fast if any single promise rejects. To inspect every outcome and handle errors individually, use Promise.allSettled():
1 const userIds = [ 1 , 2 , 9999 , 4 ] ; // 9999 is an invalid ID
2
3 const results = await Promise . allSettled (
4 userIds . map ( id => fetchUserById ( id ) )
5 ) ;
6
7 results . forEach ( ( result , index ) => {
8 if ( result . status === 'fulfilled' ) {
9 console . log ( ` User ${ userIds [ index ] } loaded: ` , result . value . name ) ;
10 } else {
11 console . warn ( ` User ${ userIds [ index ] } failed: ` , result . reason . message ) ;
12 }
13 } ) ;
Interactive Quiz • Knowledge Check
Practice Quiz What happens in modern Node.js environments when a Promise rejection is left unhandled?
A It is quietly ignored
B The Node.js process terminates with a non-zero exit code
C It retries automatically
D It enters an infinite loop
What browser event fires whenever a Promise rejects without a local catch handler?
A window.onerror
B window.addEventListener('unhandledrejection')
C document.onpromisefail
D window.addEventListener('rejected')
Why is Promise.allSettled() preferred over Promise.all() when fetching data for independent dashboard widgets?
A It uses fewer CPU threads
B A failure in one widget does not cancel or reject the data fetching for the remaining widgets
C It runs synchronously
D It only works with GET requests
In an async function, how does a rejected Promise inside an await expression manifest?
A It returns undefined
B It throws an exception that can be caught in a try...catch block
C It triggers a browser alert
D It logs directly to console.warn
What property on each result object of Promise.allSettled() indicates whether the operation succeeded?
A result.ok (boolean)
B result.status ('fulfilled' or 'rejected')
C result.success (boolean)
D result.state