javascript - Fetch: odbiti obećanje i uhvatiti grešku ako status nije OK?
Evo što imam u tijeku:
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;
}
UREDI: Obećanje se ne odbacuje, to pokušavam shvatiti.
Koristim ovaj fetch polyfill u Reduxu s redux-promise-middlewareom.
Odgovor #1
Fetch obećanja odbijaju se samo s TypeError kada dođe do mrežne pogreške. Budući da odgovori 4xx i 5xx nisu mrežne pogreške, nema ničega što bi se moglo uhvatiti. Morat ćete sami baciti pogrešku kako biste koristili Promise#catch.
Fetch Response prikladno pruža ok, što vam govori je li zahtjev uspio. Nešto poput ovoga bi trebalo funkcionirati:
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)
});
Odgovor #2
Sljedeći prijava s korisničkim imenom i lozinkom primjer pokazuje kako:
- Provjeri
response.ok rejectako nije OK, umjesto da baca grešku- Daljnje procesuirati sve tragove pogrešaka sa servera, npr. probleme s provjerom valjanosti
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);
})
});
},
Odgovor #3
Za mene, fny odgovori zapravo obuhvaćaju sve. budući da fetch ne baca grešku, moramo sami baciti/obraditi grešku. Objavljujem svoje rješenje s async/await. Mislim da je to izravnije i čitljivije
Rješenje 1: Ne bacati pogrešku, sami rukovati pogreškom
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);
}
Ovdje, ako dobijemo pogrešku, stvaramo objekt pogreške, običan JS objekt i vraćamo ga, mana je što ga moramo obraditi izvana. Kako koristiti:
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
Rješenje 2: Bacanje pogreške, korištenje 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);
}
Ovdje bacamo pogrešku koju smo stvorili, budući da konstruktor Error prihvaća samo string, stvaram običan Error JS objekt, a upotreba će biti:
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
}
Rješenje 3: Korištenje korisničke pogreške
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);
}
I :
class ClassError extends Error {
constructor(message = 'Something went wrong', data = '', code = '') {
super();
this.message = message;
this.data = data;
this.code = code;
}
}
Nadam se da je pomoglo.
Odgovor #4
Hvala svima na pomoći, odbacivanje obećanja u .catch() riješilo je moj problem:
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;
}
Odgovor #5
Za TypeScript ono što radim jest da napišem fetch omotač koji prima generički tip i ako je response ok automatski poziva .json() i izvršava tipizaciju rezultata, inače omotač baca 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>;
};
i tada ću uhvatiti pogreške i provjeriti jesu li instanceof Response. Na taj način TypeScript zna da error ima Response svojstva poput status statusText body headers itd. i mogu primijeniti prilagođenu poruku za svaki 4xx 5xx statusni kod.
try {
return await fetcher<LoginResponse>("http://localhost:8080/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "[email ", 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}`);
}
i ako se dogodi nešto poput mrežne pogreške, to se može uhvatiti izvan provjere instanceof Response s općenitijom porukom, npr.
throw new Error(`Something went wrong: ${error.message || error}`);
