Throwing Errors And A Getjson Helper

The last post ended with a real gap: a 404 is still a fulfilled Promise, so .catch() never fires for it. Here's the actual fix.

July 11, 20264 min read10 / 22

The last post ended with a real gap: a 404 is still a fulfilled Promise, so .catch() never fires for it. Fixing that turns out to fix another gap too, the one from two posts back where a missing neighbor silently broke the chain.

response.ok Tells the Real Story

A response object carries an ok property, true for a status like 200, false for anything like 404. That's the piece .catch() has no way to see on its own.

JavaScript
fetch('https://countries-api-836d.onrender.com/countries/name/asdkjfhaskjdfh').then( (response) => console.log(response.ok, response.status) );

Run that against a country name that doesn't exist and it logs false 404. The request still completed, technically, so the Promise is still fulfilled, response.ok is the only signal that anything actually went wrong.

Throwing an Error Rejects the Promise on Purpose

throw inside a .then() callback does something specific: it immediately rejects the Promise that callback returns, the same rejection a real network failure would cause, just triggered on purpose.

JavaScript
fetch('https://countries-api-836d.onrender.com/countries/name/asdkjfhaskjdfh') .then((response) => { if (!response.ok) throw new Error(`Country not found (${response.status})`); return response.json(); }) .catch((error) => console.error(error.message));

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.

That thrown error travels down the chain exactly like any other rejection, all the way to whichever .catch() is waiting for it. There's nothing special about it once it's thrown, throw is just how a .then() callback creates a rejection on demand instead of waiting for one to happen naturally.

Fixing the Guard Clause From a Few Posts Back

That gap from a few posts back, if (!neighborCode) return;, quietly passed undefined down the chain instead of a Promise. The fix is the same technique: throw instead of returning silently.

JavaScript ยท Live Editor
Loading editor...

Hit Run, then edit 'portugal' to 'australia', an island with no land borders. Instead of the old crash trying to read a flag off undefined, a real, readable message renders: this country has no land neighbors. Same fix, same technique, both places.

A Reusable getJSON Helper

That getJSON function above exists because the fetch-check-parse sequence, fetch, check response.ok, throw or call .json(), was about to get written out twice, once for the country and once for the neighbor. Wrapping it once and passing in the URL and a custom error message keeps that logic in exactly one place.

Every call site gets to skip straight to the data, the error checking already happened inside getJSON itself.

The Essentials

  1. response.ok is false for an error status like 404, even though the Promise itself is still fulfilled.
  2. throw inside a .then() callback rejects that step's Promise on purpose, the same as a real network failure would.
  3. A thrown error propagates down the chain to whichever .catch() is waiting, no different from any other rejection.
  4. A guard clause that silently returns should throw instead, so a missing value becomes a real, catchable error rather than a quiet undefined.
  5. Repeating the same fetch-check-parse steps at every call site is exactly what a small helper function like getJSON exists to avoid.