Who

If I throw it they will catch

Don’t throw HTTP responses as exceptions. 

I don’t know which mental model will resonate, but

  • Your library doesn’t know if an HTTP response is an exception, I do. 

  • Not all errors are exceptional situations. 

  • Not all happy-paths are success-paths. 

  • Don’t mix exception-handling code with response-handling code. 

  • Demos and tutorials can get away with bad practices to emphasize a different point, but production code should always check the “status” and handle errors accordingly. 

  • It’s just three things: “status,” “headers,” and “payload.”  Give it to me.  I know what I’m doing. 

  • Don’t use exceptions for expected control flow. 

Consider these two equivalent implementations:

Response is not thrown

Handling a non-2xx response as normal, not exceptional:

Response thrown

Handling a non-2xx response as exceptional / abnormal:

try {

  const response = await fetch(movieUrl(movieId));

  if (response.ok) {
    const movie = await response.json();
    showMovie(movie);
  } else if (response.status === 404) {
    showNotFound();
  } else {
    showGenericError("Couldn't load movie.", response);
  }

} catch(error) {
  report(error);
  showGenericError("Something unexpected happened."
    + " We've notified our team and they'll look into it.");
}
try {

  const response = await fetchWhichThrows(movieUrl(movieId));
  const movie = await response.json();
  showMovie(movie);

} catch(error) {
  const response = error instanceof HttpResponse && response;

  if (response && !response.ok && response.status === 404) {
    showNotFound();
  } else if (response && !response.ok) {
    showGenericError("Couldn't load movie.", response);
  } else {
    report(error);
    showGenericError("Something unexpected happened."
      + " We've notified our team and they'll look into it.");
  }
}

They’re similar, but one is clearly worse.  And this is close to a best-case for that code.  Code in the wild will have inverted conditionals and more “it isn’t pretty, but it works” readability issues, not just the inline messages to the user like this code. 

Even in this contrived case the second implementation would call that exception-handling logic with exceptions for which some of those handlers are inappropriate.  The “best-practice” is typically taught to wrap the try/catch around just enough code for the particular handler, splitting it into multiple try/catch blocks, but in this case just separating the response-handling from the exception-handling avoids that silliness altogether. 

Fun exercise

Modify the above examples to handle a 204 No Content response.  Which code adapts to that change better?