Throwing if fetch() returns response.ok === false?? Terrible!
José Pablo Ramírez Vargas

José Pablo Ramírez Vargas @webjose

Location:
Heredia, Costa Rica
Joined:
Sep 5, 2022

Throwing if fetch() returns response.ok === false?? Terrible!

Publish Date: Dec 4 '24
0 14

Can anyone think of a good reason why the masses of Javascripters decided that the norm is to throw an error on non-OK HTTP responses?? Because I can't.

To me, this is a terrible practice, and everyone just seems hypnotized by it, like moths to the flame.

I hear you all, potential moths. 😄

Comments 14 total

  • Randall
    RandallDec 4, 2024

    I don't think this is a norm in the general case. But if you're expecting an okay response and you don't get one, it makes sense to throw an error. If you're expecting a non-okay response, then it doesn't make sense to throw an error.

    • José Pablo Ramírez Vargas
      José Pablo Ramírez VargasDec 4, 2024

      Hello, thanks for dropping by.

      Not sure I follow. Shouldn't we all expect at least one non-OK response in the majority of cases? Or maybe all cases? I mean, which API is only expected to return 200? For example, saving data typed by user is prone to errors. We should always expect 200/400 as a minimum, right? Also, if your API is microservices, you should also always expect 504 with a body that describes the reason for unavailability.

      This is my line of thinking. This is why I don't understand what you say: I always should have in mind that the happy path is not the only path, right?

      Also, look at ky or axios: They both throw on non-OK responses, regardless of me having in mind the possibilities. This is why I hate these packages.

      • Randall
        RandallDec 4, 2024

        It's often the case that a GET requests is only ever expected to respond with a 200 status.

        Of course any request can return 5xx errors, but it's often not pragmatic to have your code "expect" that, in the sense of having specific handling built for it rather than using error (exception) handling. If you throw an error in that case (and the error may include the response from the service, so it can be logged, shown to the user, etc) then you can generically bubble that error (or any other) up to the user so they can can see an error message and try again later. Your logging service can pick up the error generically (including stack trace) and log it, report it to error reporting, etc, and it can get looked into (or muted, if that's the right call). With some tooling, this can be made to happen easily for any error the application throws. That's more cumbersome to achieve without using errors.

        I agree with you on axios, that's actually the thing I like least about axios (though I'm aware you can change that with non-default configuration). With fetch, I can examine the status and other response characteristics, and make a conscious decision whether to treat it as an error or not.

    • Eric Bieszczad-Stie
      Eric Bieszczad-StieDec 8, 2024

      I think most cases where developers expect a non-ok response, should actually be approached differently, or the API should be designed differently. Do you have an example of where people expect non-ok responses?

      • Randall
        RandallDec 8, 2024

        How about an API to get a dev.to post by slug: GET /posts/throwing-if-fetch-returns-responseok-false-terrible-4oh6 which returns 200 if successful, 404 if the post does not exist.

        • Eric Bieszczad-Stie
          Eric Bieszczad-StieDec 8, 2024

          The 200 response is the expected response, the "happy path". There are no cases where I GET a post that I expect to not exist. If you want to check whether it exists, then you should instead have a different endpoint that returns a 200 with the value true/false for whether it exists or not.

          I do think that you should handle errors, for example 404 errors, but I don't think you should specifically query an endpoint because you want to receive a 404, or some other non-ok code.

          • Randall
            RandallDec 8, 2024

            That doesn't sound completely unreasonable, but a separate "existence" endpoint would not be REST-ful and you would still have the possibility of the article existing when you call the existence endpoint, but not existing when you call the get article endpoint (even if only moments later).

            I would draw a distinction between expecting a certain response and wanting a certain response. Consider a web crawler that knows about a list of dev.to articles and wants to download them all. Some of them have already been deleted, so the endpoint would return a 404 for those. The crawler wants a 200 response so it can download the article, but it should also expect and understand 404s too, and skip those articles. Anything else, it could queue to retry some number of times before giving up.

            • Eric Bieszczad-Stie
              Eric Bieszczad-StieDec 8, 2024

              I agree with your distinction between wanting and expecting. In your original comment, I thought you meant that sometimes you want a non-OK response code. In my opinion, if you're wanting a non-OK response code, then probably you're really requesting some other information than what that endpoint is explicitly serving.

      • José Pablo Ramírez Vargas
        José Pablo Ramírez VargasDec 8, 2024

        Hello! The typical case is a 400 BAD REQUEST error when you POST/PUT/PATCH and the server complains about the data (data too long or out of range, etc.). Data validation is an expected scenario.

        • Eric Bieszczad-Stie
          Eric Bieszczad-StieDec 8, 2024

          I clarified my comments as a comment to Randall. I agree that non-OK codes should be expected, but I believe they never should be wanted.

  • Eric Bieszczad-Stie
    Eric Bieszczad-StieDec 8, 2024

    When you request data from an API, there is an expected response from that API. If you're fetching a User, then you expect to get a User. If you don't get a User back for whatever reason, it makes sense to handle this as an error, or at least a "not ok response" - you didn't get what you want.

    Some people might just be lazy and throw an error instead of handling it, but at least then it's very clear when something goes wrong at the exact point where it goes wrong (and not at a later time because suddenly your data is an error object and not a User) and you know where to add error handling later. It could also be expected that the error is not meant to be handled there and should be propagated and handled outside of the module (like in the case of axios where you are expected to catch and handle errors). With axios, I believe you can register a global error handler too, so that any error gets handled. That's useful for websites where at the very least, any API errors can get shown to the user.

    I'm not too experienced with APIs, so maybe you're right, but I wanted to provide a different view on it to help foster discussion.

  • Eric Haynes
    Eric HaynesDec 10, 2024

    Throwing AT ALL is terrible. 😄 Particularly in TS because you can't even apply types to the caught error.

    At most, you should throw when the program is not working as expected. People use it for control flow, which is awful. fetch is doing the appropriate thing: it throws an error if the http transaction failed, but if you successfully made a round trip to the other side, it gives you the response. It's a disjoint union of the possible outcomes. It's up to the application to decide if a 404 is really a failure, e.g. if the user's account is mysteriously gone, but not if an image has been deleted.

Add comment