Skip to content

Instantly share code, notes, and snippets.

@odewahn
Last active February 27, 2024 09:56
Show Gist options
  • Save odewahn/5a5eeb23279eed6a80d7798fdb47fe91 to your computer and use it in GitHub Desktop.
Save odewahn/5a5eeb23279eed6a80d7798fdb47fe91 to your computer and use it in GitHub Desktop.
Processing errors with Fetch API

I really liked @tjvantoll article Handling Failed HTTP Responses With fetch(). The one thing I found annoying with it, though, is that response.statusText always returns the generic error message associated with the error code. Most APIs, however, will generally return some kind of useful, more human friendly message in the body.

Here's a modification that will capture this message. The key is that rather than throwing an error, you just throw the response and then process it in the catch block to extract the message in the body:

fetch("/api/foo")
  .then( response => {
    if (!response.ok) { throw response }
    return response.json()  //we only get here if there is no error
  })
  .then( json => {
    this.props.dispatch(doSomethingWithResult(json)) 
  })
  .catch( err => {
    err.text().then( errorMessage => {
      this.props.dispatch(displayTheError(errorMessage))
    })
  })

Frankly, I'm horrified that JavaScript let's you throw some random value, rather than an error, but hey, when in Rome...

@najibla
Copy link

najibla commented Nov 26, 2020

With Express, I am doing the following

export async function fetchData(token: any, body: any) {
  const result = await fetch(
    `${config.apiUrl}/${config.projectKey}/my-request`,
    {
      method: 'POST',
      headers: {
        Authorization: token,
      },
      body: JSON.stringify(body),
    }
  );

  const json = await result.json();
  if (!result.ok) {
    throw {
      statusCode: result.status,
      ...json
    };
  }
  return json;
}

and in the route I do

router.post(
  '/some-path',
  handleErrorAsync(async (req: Request, resp: Response, _err: Errback) => {
    const data = await fetchData(req.get('authorization'), req.body);
    resp.json(data)
  })
);

And I have an error handling package that does

// async functions errors handler to avoid using try catch for every async route

export const handleErrorAsync = (func: Function) => (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  func(req, res, next).catch((error: Error) => {
    next(error);
  });
};

// middleware to respond with an error when error caught
export function handleError(
  err: any,
  _req: Request,
  resp: Response,
  _next: NextFunction
) {
  if (err) {
    resp.status(err.statusCode || 500).json(err);
  }
}

And of course I add to the router

router.use(handleError);

@ghmcadams
Copy link

This could be simplified with the following:

return fetch(url)
    .then(res => {
        if (!res.ok) {
            return res.json().then(json => { throw json; });
        }
        return res.json();
    })
    .catch(err => {
        // err is not a promise
    });

@AnthonyZJiang
Copy link

I found this is easier to read:

return fetch(url)
    .then(async (res) => {
        if (!res .ok) {
            throw await res.json();
        }
        return response.json();
    })
    .catch(err => {
        // err is not a promise
    });

@szuecs-alex
Copy link

var handlejson = async function(x){
		var t = await x.text();
		try{
			return JSON.parse(t);
		}catch(e){				
			console.log("unparsed:",t);
			return null; //or throw new Expection(e);
		}
	}
fetch(...).then(handlejson)

@ariccio
Copy link

ariccio commented Jan 18, 2023

Oh I bet yall will love this extremely cursed wrapper I wrote for fetch a few years ago, it lets me bubble up the exact network error to the UI. With this monstrosity, I can get the actual error (e.g. ECONNREFUSED when the server isn't running!):

https://github.com/ariccio/COVID-CO2-tracker/blob/main/co2_client/src/utils/FetchHelpers.ts#L312

@commonpike
Copy link

Actually, I think the node-fetch docs propose a nice solution for the OP ?

Instead of throwing a regular Error, they throw a custom extension of Error that contains the response. In the catch clause, they access the response:

https://github.com/node-fetch/node-fetch?tab=readme-ov-file#handling-client-and-server-errors

class HTTPResponseError extends Error {
	constructor(response) {
		super(`HTTP Error Response: ${response.status} ${response.statusText}`);
		this.response = response;
	}
}
...
	if (!response.ok) {
		throw new HTTPResponseError(response);
	}
...
catch (error) {
	console.error(error);
	const errorBody = await error.response.text();
	....
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment