fetch does not reject on a 404

fetch rejects on a network failure and resolves on every HTTP response, including 404 and 500. Code that only has a catch therefore treats an error page as success and tries to parse it as JSON.

fetch('/api/orders/91')
  .then(res => {
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}`);
    }
    return res.json();
  })
  .catch(report);

res.ok is true for 200-299 and nothing else, so the three-line guard belongs in a wrapper rather than at every call site. This is the single most common bug in code migrating off jQuery, where a non-2xx status went to the error callback. Worth reading the body before throwing, too — an API that returns a useful error message in JSON deserves to have it surfaced.