DEV Community

Cover image for Safe Data Fetching in Modern JavaScript

Safe Data Fetching in Modern JavaScript

Steve Sewell on January 16, 2023

Fetch - the wrong way fetch in JavaScript is awesome. But, you may have something like this sprinkled throughout your code: const ...
Collapse
 
webjose profile image
José Pablo Ramírez Vargas
try {
  const res = await fetch('/user')

  if (!res.ok) {
    throw new ResponseError('Bad fetch response', res)
  }

  const user = await res.json()
} catch (err) {
  // Handle the error, with full access to status and body
  switch (err.response.status) {
    case 400: /* Handle */ break
    case 401: /* Handle */ break
    case 404: /* Handle */ break
    case 500: /* Handle */ break
  }
}

export function handleError(err: unkown) {
  // Safe to our choice of logging service
  saveToALoggingService(err);

  if (err instanceof ResponseError) {
    switch (err.response.status) {
      case 401:
        // Prompt the user to log back in
        showUnauthorizedDialog()
        break;
      case 500: 
        // Show user a dialog to apologize that we had an error and to 
        // try again and if that doesn't work contact support
        showErrorDialog()
        break;
      default:
        // Show 
        throw new Error('Unhandled fetch response', { cause: err })
    }
  } 
  throw new Error('Unknown fetch error', { cause: err })
}
Enter fullscreen mode Exit fullscreen mode

Constructions like this one where logic is driven by exceptions is an anti-pattern. This should be avoided. Unwinding the call stack is costly (probably in all modern languages).

Collapse
 
simonireilly profile image
Simon

Friends don't let friends use errors for flow control 👍

Collapse
 
synneks profile image
Denis Eneotescu

Can you explain a bit better, I don't understand what you are trying to say. How else would you handle each response status?

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

An HTTP response is just that, a response coming from the wire. Its status code is meaningless and its interpretation is given by the receptor. By itself, status code is just a number. There is no technical reason to raise an exception (throw an error) if a particular number is received.

This is particularly true when consuming RESTful API's. You may receive a 404 indicating the resource you're looking for is not found. Just because something isn't found we need to throw an error? Hell no.

Collapse
 
philw_ profile image
Phil Wolstenholme

+1 for Wretch :) For anyone evaluating options, there's github.com/sindresorhus/ky which is a similar wrapper too.

Collapse
 
iamdevmarcos profile image
Marcos Mendes

very nice post, + 1 follower

Collapse
 
willmand profile image
willmand

Excelente. Gracias

Collapse
 
alchemylab profile image
Luigi Pesce

A good explanation around fetching and posting data

Collapse
 
vishal8888a8 profile image
vishal giri

Great Post

Collapse
 
tohka200213 profile image
tohka20-0213 • Edited

Thanks for the great post!

I have a question about this sentence.

We can’t actually access res in the catch block, so at the time of processing the error we don’t actually know what the status code or body of the response was.

Is there any problem with the code of using Error.prototype.cause?
Below is a sample code.

try {
  const res = await fetch("/user");
  if (!res.ok) {
    throw new Error("Bad fetch response", {
      cause: {
        type: "response",
        value: res.status,
      },
    });
  }
  const user = await res.json()
} catch (err) {
  if (err.cause.type === "response") {
    switch (err.cause.value) {
        case 400: /* Handle */ break
        case 401: /* Handle */ break
        case 404: /* Handle */ break
        case 500: /* Handle */ break
    }
  }
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
steve8708 profile image
Steve Sewell • Edited

So I frankly like this solution, and even made a video suggesting it, but some believe it is an anti pattern so I suggested to use a custom Error subclass in this post instead. I think jury is out if it’s wrong to use the cause property for things that aren’t errors

Collapse
 
tohka200213 profile image
tohka20-0213

Thanks for your reply!

but some believe it is an anti pattern so I suggested to use a custom Error subclass in this post instead.

What points and reasons do they believe it is an anti-pattern?

Thread Thread
 
steve8708 profile image
Steve Sewell

The idea is that cause is supposed to be used for an Error, like described here v8.dev/features/error-cause, as it's main intent is when an error with a cause is thrown, the original error stack trace is printed along with it

Now, in JS, technically you can throw anything, like throw 'hello' or throw 1234 but it's pretty heavily discouraged. Some replies to my video pointed out that because you can throw anything, the cause property must allow anything, but it similarly is best used for only error instances. Plus the benefit of errors in JS is that you can put any properties you want on them, like error.response or whatever, so I thought that was a fair suggestion to use that just to be safe and stick to continuing to use cause just for actual errors

I think this all depends a bit on how browsers handle visualizing error causes though, which I have not played with in detail myself, so I think time will really tell on what the best patterns actually are here

Collapse
 
d3press3dd profile image
Anthony Rosman

Nice article

Collapse
 
elsyng profile image
Ellis • Edited

Nice post.

A js alternative which i find simpler to use:
a fetchXyz() function which returns an object like: { result, error }
where error could be a simple string.

Then you use it like:

const { result, error } = await fetchXyz(url);
if (error) --> handle the error
else --> handle your data
Enter fullscreen mode Exit fullscreen mode