0

Our API server returns JSON data with error responses. I could not find a standard way of handling JSON data on error handling methods. my current solution is this. It is working, but I want to handle errors in catch() method not in then();

let url = 'http://localhost:8080';
    let data = {'field': 'value'};
    fetch(url, {
      method: 'PUT',
      body: JSON.stringify(data),
      credentials: 'same-origin',
      mode: 'cors',
      headers: {
        'content-type': 'application/json',
        'accept': 'application/json'        
      }
    })
      .then(res => {
        if (res.status == 400) {
          return res.json();
        } else if (!res.ok) {
          throw (res);
        } else {
          return res.json();
        }
      }).then(data => {
        if (data.status == 400) {
          throw (data);
        }
        return (data);
      }).catch(err => {
        if (err.status == 400) {
          throw this.handleError(err); 
        } else {
          throw new Error(`HTTP Error ${err.status}`);
        }
      });

this is an example of JSON response from server.

{
    "parameters": {
        "type": {
            "isEmpty": "Field is required and cannot be empty"
        },
        "from": {
            "isEmpty": "Field is required and cannot be empty"
        },
        "to": {
            "isEmpty": "Field is required and cannot be empty"
        }
    },
    "title": "Invalid parameter",
    "type": "/api/doc/invalid-parameter",
    "status": 400,
    "detail": "Invalid parameter"
}

8
  • oh ... so the JSON includes the status ... then it's easy if the status in JSON is always the same as the response status - return Promise.reject(res.json()) if res.status == 400 Commented Apr 16, 2019 at 9:36
  • It only returns JSON for a 400 response? Commented Apr 16, 2019 at 9:37
  • No this is just an example. It returns JSON for other error types. I will expand the code to handle them. Commented Apr 16, 2019 at 9:39
  • But you're saying you accept json, so I assume it returns json for successful responses too? Commented Apr 16, 2019 at 9:40
  • this code is included in a function. it returns this fetch promise. so you add another .then() to handle resolved data. Commented Apr 16, 2019 at 9:44

1 Answer 1

1

I would make a thin wrapper around fetch which throws on >= 400 responses with parsed body else parses successful responses.

function parse(res) {
  const contentType = res.headers.get('Content-Type') || '';
  const isJson = contentType.includes('application/json');
  return isJson ? res.json() : res;
}

async function throwOnError(res) {
  if (res.status >= 400) {
    const err = new Error(res.statusText || 'Internal Server Error');
    err.status = res.status;
    const parsedRes = await parse(res);
    err.body = parsedRes;
    throw err;
  }

  return res;
}

async function fetchWrapper({ method, url, data, headers }) {
  const combinedHeaders = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  };

  if (headers) {
    Object.assign(combinedHeaders, headers);
  }

  const options = {
    credentials: 'same-origin',
    mode: 'cors',
    method,
    headers: combinedHeaders,
  };

  if (data) {
    options.body = JSON.stringify(data);
  }

  return fetch(url, options)
    .then(throwOnError)
    .then(parse);
}

const queryParams = (params) =>
  Object.keys(params)
    .filter(k => params[k] !== null && typeof params[k] !== 'undefined')
    .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
    .join('&');

export const appendUrlParams = (url, params) => (params ? `${url}?${queryParams(params)}` : url);

export const $get = (url, params, { ...options }) =>
  fetchWrapper({ method: 'GET', url: appendUrlParams(url, params), ...options });

export const $del = (url, params, { ...options }) =>
  fetchWrapper({ method: 'DELETE', url: appendUrlParams(url, params), ...options });

export const $post = (url, data, { ...options }) =>
  fetchWrapper({ method: 'POST', url, data, ...options });

export const $put = (url, data, { ...options }) =>
  fetchWrapper({ method: 'PUT', url, data, ...options });

e.g.

async function fetchSomething() {
  try {
    const res = await $get('someurl');
    // Do something with successful `res`.
  } catch (err) {
    console.error(err);
    // err.status -> the status of the response
    // err.body -> the body of the response
  }
}

Or use then/catch if that's your preference.

Sign up to request clarification or add additional context in comments.

1 Comment

thanks. I was expecting a simpler solution. In comments section this is solved. I was insisting on returning promises. May be a more elegant solution would be wrapping fetch in an async function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.