Try Catch With Async Await

The last post left async/await with no error handling at all. .catch() doesn't attach to it the same way, but try/catch, plain JavaScript from long before Promises existed, does.

July 11, 20264 min read18 / 22

The last post left whereAmI with no error handling at all. .catch() doesn't attach to await the way it attaches to .then(), but try/catch does the job, and it isn't even an async feature.

try/catch Predates async/await Entirely

try/catch has been in JavaScript since long before Promises existed. It wraps ordinary code, catches whatever error happens inside, and keeps the script from dying because of it.

JavaScript · Live Editor
Loading editor...

Hit Run. Reassigning a const is a real error, age is not just a suggestion, and without the try block it would stop the script entirely. Inside one, the catch block gets the error object and the script keeps going.

Wrapping whereAmI in try/catch

Nothing about the async function's logic changes, the whole body just moves inside a try block, and a catch block replaces the missing error handling from last post.

JavaScript · Live Editor
Loading editor...

Hit Run. A genuine failure, permission denied, a rate-limited geocoding request, no longer takes the whole function down with it, it lands in catch and gets shown to the user instead.

The Same Old Gotcha, Still True Here

That manual if (!resGeo.ok) throw new Error(...) line exists for the exact reason from post 10: fetch only rejects on a real network failure, never on a bad status code. A 403 from hitting the geocoding API's rate limit is still a fulfilled response as far as fetch is concerned, async/await changes nothing about that fact, it just changes how the check reads.

The country lookup doesn't need its own check here, getJSON already throws internally, and a throw anywhere inside a try block, including one nested inside a function try is calling, lands in that same catch.

A rejection propagates down through the chain until something catches it, whether that rejection came from a real network failure or a manual throw. ExpandA rejection propagates down through the chain until something catches it, whether that rejection came from a real network failure or a manual throw.

A thrown error works the same way here: it doesn't matter how deep inside the try block it happens, it propagates outward exactly like a Promise rejection does, until the nearest catch picks it up.

finally Still Works the Same Way

try/catch has an optional third block, finally, and it runs whether the try succeeded or the catch caught something, the exact same job .finally() did on a Promise chain. The opacity fade-in belongs there for the same reason it always has: it has to happen either way.

The Essentials

  1. try/catch is plain JavaScript, older than Promises, not something built specifically for async/await.
  2. Wrapping an async function's body in try and adding a catch block is the actual replacement for .catch().
  3. fetch still only rejects on a real network failure, an if (!response.ok) throw new Error(...) check is still needed for a bad status, async/await doesn't change that rule.
  4. A throw inside a try block lands in that block's catch, whether it happens directly or inside a function the try block calls.
  5. finally runs regardless of success or failure, same job as a Promise chain's .finally(), just spelled differently.