Search code examples
corsfetch-apireasonbucklescriptreason-react

Fetch rejects promise on 404 responses instead of resolving with 404 status


I'm trying to figure out how to process failed http responses in the fetch example in reason-react-example repo.

The following was my first idea (tinkering with the url):

Js.Promise.(
          Fetch.fetch("https://dog.ceo/api/breeds/list")
          |> then_(res => {
               let ok = Fetch.Response.ok(res);
               ok ?
                 Fetch.Response.json(res) :
                 Js.Exn.raiseError(Fetch.Response.statusText(res));
             })
          |> then_(json =>
               json
               |> Decode.dogs
               |> (dogs => self.send(DogsFetched(dogs)))
               |> resolve
             )
          |> catch(err => {
               Js.log(err);
               [%bs.raw {| console.log(err.response) |}]
               Js.Promise.resolve(self.send(DogsFailedToFetch));
             })
          |> ignore
        );

It's not working as I hoped. It turns out Fetch rejects right away when the HTTP request fails with 404 for instance, which I didn't expect since it's not how the browser fetch API works. Moreover, when err is logged it's TypeError: Failed to fetch and the err.response property is undefined.

My question is: how to process the error to get the status code and status text for example?


Solution

  • The problem is CORS. The server you're using will include an Access-Control-Allow-Origin: * header with 2xx responses, but not with 404 responses. So apparently you're only allowed to read successful responses from this server.

    You should get some additional warnings or errors in the console that tells you something like:

    Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://dog.ceo/api/breeds/listio. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

    or

    [Error] Origin null is not allowed by Access-Control-Allow-Origin.

    [Error] Failed to load resource: Origin null is not allowed by Access-Control-Allow-Origin. (listio, line 0)

    [Error] Fetch API cannot load https://dog.ceo/api/breeds/listio. Origin null is not allowed by Access-Control-Allow-Origin.

    You can fix this, at least when just testing, by using no-cors mode instead. With bs-fetch you'd do:

    Fetch.fetchWithInit(url, Fetch.RequestInit.make(~mode=Fetch.NoCORS, ()))