Search code examples
javascriptnode.jserror-handlinges6-promise

Within a fetch() Promise, how to .catch server errors messages when status is 4xx or 5xx?


In a locally run Node.js script, this works when status is 200:

// module file
import fetch from "node-fetch";
export const getJSON = () => {
  const url = 'https://api.somesite.com/api/v0/etc';
  const options = {method: 'GET', headers: {Accept: 'application/json'}};
  const request = fetch(url, options)
    .then(response => response.json())
    .catch(err => console.log("somesite:", err));
  return Promise.resolve(request);
};

// execution file
import { getJSON } from './libs/api_requests.mjs';
console.log("func call", await getJSON());

But the fetch also works without triggering the .catch logic when the response status is 4xx or 5xx (see for example this answer).

Execution doesn't break and I actually receive an error message when the function is called as if that would be the correct, normal result - as the output of response.json().

This message is in plain English, something like "error: 'Incorrect path. Please check https://www.somesite.com/api/'".

I would like to preserve/display this error message, only I would like to catch it within the function getJSON in the module file, instead of having to wrap some logic around it at the destination, potentially repeating the same code multiple times everywhere the function is called, instead of dealing with the issue just once at the source.

So I modified the .then clause like this, which also works:

.then(response => { if (response.ok) { // .ok should be status 200 only, I suppose
        return response.json();
      } else { throw new Error(response.status) }

This now triggers the .catch clause as intended, displaying "Error: 404 [etc]". Except what I would like to throw is the original error message "Incorrect path [etc]" and that I could not do. I tried

.then(response => { if (response.ok) {
      return response.json();
    } else { throw new Error(response.json()) } // somesite: Error: [object Promise]

.then(response => { if (response.ok) {
      return response.json()
    } else { throw new Error(Promise.resolve(response.json())) } // somesite: Error: [object Promise]

.then(response => { if (response.ok) {
      return response.json()
    } else { throw new Error(return response.json()) } // SyntaxError: Unexpected token 'return'

.then(response => { if (response.ok) {
        return response.json();
      } else { throw new Error(Promise.resolve(request)) } // somesite: Error: [object Promise]

I guess I need to resolve the response.json() promise as if all was ok, but how to do that?

I also had a look at the request object with console.dir(request, { depth: null }) to see if I could extract the error message from there, but I couldn't find it and the object still contained many unexpanded elements like [Function: onerror] or [Function: onclose] for example.


Solution

  • Try response.text() instead of response.json() when the status code is 400 or 500. In my experience, the error messages are typically returned by the text callback.


    See this answer to a similar question.


    Edit:

    Added the following code, suggested by OP.

    .then((response) => {
      if (response.ok) {
        return response.json();
      }
      else {
         return response.text()
           .then((text) => {
             throw(text);
    
             // if the error is an object and you just want to display some elements:
             throw(JSON.parse(text));
           });
      }
    })
    .catch((err) => {
      // in case you want to log the error
      console.log("somesite: ", err));
    
      return new Error("somesite: " + err);
    });