Better Failures

When you bring in web services, your application gains new ways to fail. You need to be able to detect these errors and either recover from them or communicate them to the viewer. Exceptions are a common mechanism for handling errors in synchronous code. You might think you can handle an error with fetch using a try-catch block:

html
js
preview

You are wrong. Try-catch is used to handle exceptions that occur synchronously in the try block. The fetch function runs asynchonously. To handle asynchronous exceptions, you use the catch function provided by the promise that fetch returns:

html
js
preview

The callback provided to catch here just echoes the unhelpful error message in the pre element. A website in production must provide a more readable message and give the viewer a way forward.

The above error is caused by a URL that doesn't point to a real domain. What happens if the URL points to a bad endpoint on a valid domain?

html
js
preview

Do you see an error message? Try popping open this page and inspecting the console.

You see an error message, but it doesn't bubble up into your catch-handler. That's funny. You add a then-handler to your promise chain to inspect this issue further:

html
js
preview

Pop open this page in its window and inspect the response in the console. What do you notice?

Technically, the fetch itself did not fail. The request arrived at the service just fine, and the client received a response just fine. The only issue was that the client sent an unsatisfiable request, which the service indicated in its response by setting the status code to 404.

To handle failed requests—as opposed to failed fetches—you could add a conditional statement inside the then-handler:

html
js
preview

However, it'd be slick if you could make failed requests behave like failed fetches. Then all your error-handling could be consolidated in your callback to catch. As you think about the problem, you think back to the chaining mechanics of promises, and you come up with this assertResponse function:

html
js
preview

Note how assertResponse has been inserted as the first then-handler in the promise's chain. The function is only named, not called. You are passing it as a callback.

The service sends a status code in the 200s if the request was satisfied. If the response has a status code in this range, your function returns it so it can be passed along to the next stage in the promise's chain. Otherwise, an Error object is thrown. The second then-handler is skipped over and the catch-handler runs.

The error handling code is consolidated, and the then-handler is simplified since it can assume that it's receiving a successful response. You decide to insert assertResponse as the first stage of all your fetch calls for the rest of your career as a web developer.