Fetch: reject promise and catch the error if status is not OK?

JavascriptReduxFetch Api

Javascript Problem Overview


Here's what I have going:

import 'whatwg-fetch';

function fetchVehicle(id) {
	return dispatch => {
		return dispatch({
			type: 'FETCH_VEHICLE',
			payload: fetch(`http://swapi.co/api/vehicles/${id}/`)
				.then(status)
				.then(res => res.json())			
				.catch(error => {
					throw(error);
				})
			});
	};
}

function status(res) {
	if (!res.ok) {
		return Promise.reject()
	}
	return res;
}

EDIT: The promise doesn't get rejected, that's what I'm trying to figure out.

I'm using this fetch polyfill in Redux with redux-promise-middleware.

Javascript Solutions


Solution 1 - Javascript

Fetch promises only reject with a TypeError when a network error occurs. Since 4xx and 5xx responses aren't network errors, there's nothing to catch. You'll need to throw an error yourself to use Promise#catch.

A fetch Response conveniently supplies an ok , which tells you whether the request succeeded. Something like this should do the trick:

fetch(url).then((response) => {
  if (response.ok) {
    return response.json();
  }
  throw new Error('Something went wrong');
})
.then((responseJson) => {
  // Do something with the response
})
.catch((error) => {
  console.log(error)
});

Solution 2 - Javascript

Thanks for the help everyone, rejecting the promise in .catch() solved my issue:

export function fetchVehicle(id) {
	return dispatch => {
		return dispatch({
			type: 'FETCH_VEHICLE',
			payload: fetch(`http://swapi.co/api/vehicles/${id}/`)
				.then(status)
				.then(res => res.json())	
				.catch(error => {
					return Promise.reject()
				})
			});
	};
}


function status(res) {
	if (!res.ok) {
		throw new Error(res.statusText);
	}
	return res;
}

Solution 3 - Javascript

For me, fny answers really got it all. since fetch is not throwing error, we need to throw/handle the error ourselves. Posting my solution with async/await. I think it's more strait forward and readable

Solution 1: Not throwing an error, handle the error ourselves

  async _fetch(request) {
    const fetchResult = await fetch(request); //Making the req
    const result = await fetchResult.json(); // parsing the response

    if (fetchResult.ok) {
      return result; // return success object
    }


    const responseError = {
      type: 'Error',
      message: result.message || 'Something went wrong',
      data: result.data || '',
      code: result.code || '',
    };

    const error = new Error();
    error.info = responseError;

    return (error);
  }

Here if we getting an error, we are building an error object, plain JS object and returning it, the con is that we need to handle it outside. How to use:

  const userSaved = await apiCall(data); // calling fetch
  if (userSaved instanceof Error) {
    debug.log('Failed saving user', userSaved); // handle error

    return;
  }
  debug.log('Success saving user', userSaved); // handle success

Solution 2: Throwing an error, using try/catch

async _fetch(request) {
    const fetchResult = await fetch(request);
    const result = await fetchResult.json();

    if (fetchResult.ok) {
      return result;
    }

    const responseError = {
      type: 'Error',
      message: result.message || 'Something went wrong',
      data: result.data || '',
      code: result.code || '',
    };

    let error = new Error();
    error = { ...error, ...responseError };
    throw (error);
  }

Here we are throwing and error that we created, since Error ctor approve only string, Im creating the plain Error js object, and the use will be:

  try {
    const userSaved = await apiCall(data); // calling fetch
    debug.log('Success saving user', userSaved); // handle success
  } catch (e) {
    debug.log('Failed saving user', userSaved); // handle error
  }

Solution 3: Using customer error

  async _fetch(request) {
    const fetchResult = await fetch(request);
    const result = await fetchResult.json();

    if (fetchResult.ok) {
      return result;
    }

    throw new ClassError(result.message, result.data, result.code);
  }

And:

class ClassError extends Error {

  constructor(message = 'Something went wrong', data = '', code = '') {
    super();
    this.message = message;
    this.data = data;
    this.code = code;
  }

}

Hope it helped.

Solution 4 - Javascript

The following login with username and password example shows how to:

  1. Check response.ok
  2. reject if not OK, instead of throw an error
  3. Further process any error hints from server, e.g. validation issues
login() {
  const url = "https://example.com/api/users/login";
  const headers = {
    Accept: "application/json",
    "Content-Type": "application/json",
  };
  fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify({
      email: this.username,
      password: this.password,
    }),
  })
    .then((response) => {
	  // 1. check response.ok
      if (response.ok) {
        return response.json();
      }
      return Promise.reject(response); // 2. reject instead of throw
    })
    .then((json) => {
	  // all good, token is ready
      this.store.commit("token", json.access_token);
    })
    .catch((response) => {
      console.log(response.status, response.statusText);
      // 3. get error messages, if any
      response.json().then((json: any) => {
        console.log(json);
      })
    });
},

Solution 5 - Javascript

2021 TypeScript Answer

What I do is write a fetch wrapper that takes a generic and if the response is ok it will auto .json() and type assert the result, otherwise the wrapper throws the response

export const fetcher = async <T>(input: RequestInfo, init?: RequestInit) => {
  const response = await fetch(input, init);

  if (!response.ok) {
    throw response;
  }

  return response.json() as Promise<T>;
};

and then I'll catch errors and check if they are an instanceof Response. That way TypeScript knows that error has Response properties such as status statusText body headers etc. and I can apply a custom message for each 4xx 5xx status code.

try {
  return await fetcher<LoginResponse>("http://localhost:8080/login", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ email: "[email protected]", password: "passw0rd" }),
  });
} catch (error) {
  if (error instanceof Response) {
    switch (error.status) {
      case 401:
        throw new Error("Invalid login credentials");
      /* ... */
      default:
        throw new Error(`Unknown server error occured: ${error.statusText}`);
    }
  }
  throw new Error(`Something went wrong: ${error.message || error}`);
}

and if something like a network error occurs it can be caught outside of the instanceof Response check with a more generic message i.e.

throw new Error(`Something went wrong: ${error.message || error}`);

Solution 6 - Javascript

The answer by @fny (the accepted answer) didn't work for me. The throw new Error() wasn't getting picked up by the .catch. My solution was to wrap the fetch with a function that builds a new promise:


function my_fetch(url, args) {
  return new Promise((resolve, reject) => {
    fetch(url, args)
    .then((response) => {
      response.text().then((body) => { 
        if (response.ok) {
          resolve(body) 
        } else {
          reject(body) 
        }
      })
    })
    .catch((error) => { reject(error) })
  })
}

Now every error and non-ok return will be picked up by the .catch method:

my_fetch(url, args)
.then((response) => {
  // Do something with the response
})
.catch((error) => {
  // Do something with the error
})

Solution 7 - Javascript

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
    return response;
}
fetch("https://example.com/api/users")
    .then(handleErrors)
    .then(response => console.log("ok") )
    .catch(error => console.log(error) );

Solution 8 - Javascript

I just checked the status of the response object:

$promise.then( function successCallback(response) {  
  console.log(response);
  if (response.status === 200) { ... }
});

Solution 9 - Javascript

Hope this helps for me throw Error is not working

function handleErrors(response) {
  if (!response.ok) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject({
          status: response.status,
          statusText: response.statusText,
        });
      }, 0);
    });
  }
  return response.json();
}

function clickHandler(event) {
  const textInput = input.value;
  let output;
  fetch(`${URL}${encodeURI(textInput)}`)
    .then(handleErrors)
    .then((json) => {
      output = json.contents.translated;
      console.log(output);
      outputDiv.innerHTML = "<p>" + output + "</p>";
    })
    .catch((error) => alert(error.statusText));

}

Solution 10 - Javascript

I wasn't satisfied with any of the suggested solutions, so I played a bit with Fetch API to find a way to handle both success responses and error responses.

Plan was to get {status: XXX, message: 'a message'} format as a result in both cases.

Note: Success response can contain an empty body. In that case we fallback and use Response.status and Response.statusText to populate resulting response object.

fetch(url)
  .then(handleResponse)
  .then((responseJson) => {
    // Do something with the response
  })
  .catch((error) => {
    console.log(error)
  });

export const handleResponse = (res) => {
  if (!res.ok) {
    return res
      .text()
      .then(result => JSON.parse(result))
      .then(result => Promise.reject({ status: result.status, message: result.message }));
  }
  return res
    .json()
    .then(result => Promise.resolve(result))
    .catch(() => Promise.resolve({ status: res.status, message: res.statusText }));
};

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVlady VeselinovView Question on Stackoverflow
Solution 1 - JavascriptfnyView Answer on Stackoverflow
Solution 2 - JavascriptVlady VeselinovView Answer on Stackoverflow
Solution 3 - JavascriptTomer OmriView Answer on Stackoverflow
Solution 4 - JavascriptohhoView Answer on Stackoverflow
Solution 5 - JavascriptJarodView Answer on Stackoverflow
Solution 6 - Javascriptv4gilView Answer on Stackoverflow
Solution 7 - JavascriptMohamed FadlView Answer on Stackoverflow
Solution 8 - JavascriptTorsten BarthelView Answer on Stackoverflow
Solution 9 - JavascriptArulView Answer on Stackoverflow
Solution 10 - JavascriptmladzoView Answer on Stackoverflow