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.
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)
});
The following login with username and password example shows how to:
Check response.ok
reject if not OK, instead of throw an error
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);
})
});
},
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;
}
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.
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: "user#example.com", 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}`);
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
})
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) );
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 }));
};
I just checked the status of the response object:
$promise.then( function successCallback(response) {
console.log(response);
if (response.status === 200) { ... }
});
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));
}
Another (shorter) version that resonates with most answers:
fetch(url)
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(json => doStuff(json)) //all good
//next line is optional
.catch(response => handleError(response)) //handle error
I was following the official firebase tutorial on promises (https://www.youtube.com/watch?v=7IkUgCLr5oA) but in my case, I cannot make it work.
const promise = userRef.push({text:text});
const promise2 = promise.then((snapshot) => {
res.status(200).json({message: 'ok!'});
});
promise2.catch(error => {
res.status(500).json({message: 'error'});
});
What am I doing wrong? Each then() should have its response in case something goes wrong, but that is why I am writing the promise2 catch.
Just add the return before sending the response.
const promise = userRef.push({text:text});
const promise2 = promise.then((snapshot) => {
return res.status(200).json({message: 'ok!'});
});
promise2.catch(error => {
return res.status(500).json({message: 'error'});
});
Also you can chain the promises as follows:
return userRef.push({text:text})
.then((snapshot) => {
return res.status(200).json({message: 'ok!'});
}).catch(error => {
return res.status(500).json({message: 'error'});
});
I am trying to return a promise from dispatch so that I can do something like this in my react component
this.props.dispatch(requestLogin(data))
.then((res) => {
Navigate.toHome()
}).catch((err) => {
this.showErrorMessage()
})
currently I wrapped my fetch to reuse the common things i pass on the server API and to put some logs for debugging. I did it like this:
export const query = (path, opts) => {
// common config and boilerplates here
// e.g add device id to every api request
return fetch(opts.url, reqOpts)
.then((response) => {
console.log('response received')
if (response.ok) {
return response.json()
} else
console.log('response not ok')})
.then((respData) => {
if (respData.status === true) {
console.log('response success')
return respData
} else {
const errObj = respData
errObj.server = true
throw errObj
}
}).catch((err) => {
console.log('error catched')
if (err.server) {
throw err
}
throw { status: false, errors: { error_code: 'ERR_FATAL', error_msg: 'Something went wrong.' }, err }
})
then my action creator is like this:
export function requestLogin (data) {
return function (dispatch) {
const opts = {
method: 'POST',
body: data,
}
return query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
},
(data2) => {
// the thrown error actually returns here
// this returned value goes to the .then of the dispatch
return data2
},
).catch((err) => {
// this is not executed
return err
})
}
}
whats happening is
this.props.dispatch(requestLogin(data))
.then((res) => {
// the error actually goes here
Navigate.toHome()
}
(err) => {
// not here
}).catch((err) => {
// or here
this.showErrorMessage()
})
First, it's important to understand that the second argument you give then(onFulfilled, onRejected), which is onRejected, is another syntax to catch, so because it's written before your catch in the action creator, you get to there when the query function throws an error. that is why the catch block isn't executed. (read about promise's then).
after you catch your error in onRejected, it returns a promise, which is not an error anymore(the promise's state is fulfilled and not rejected).
if you want the promise to get to the catch block, you should change your action creator:
return query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
},
(data2) => {
// the thrown error actually returns here
// this returned value goes to the .then of the dispatch
return new Promise((resolve,reject) => {
reject(data2)
}
})
that will return a promise, which is rejected, so it will be caught by the catch block.
also, you can change the
return new Promise((resolve,reject) => {
reject(data2)
}
with
throw 'error'
or
Promise.reject(data2)
let me know if you need any further explanation.
When you doing:
query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
},
(data2) => {
// the thrown error actually returns here
// this returned value goes to the .then of the dispatch
return data2
})
.catch((err) => {
// this is not executed
return err
})
It's actually, you do catch the error of query function already, then you return data2. It means you want to return a Promise success (resolve) with data2. The same thing happen with catch.
To fix it, you just need to remove the (data2) => {} and the catch block.
query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
})
The second way, in case you still want to do something with the error before, you need to return Promise.reject:
query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
})
.catch((err) => {
// you can do something with error, and still return a promise.reject here
console.log('I found an error here', err)
return Promise.reject(err)
})
My provider makes available an API for an http.get request:
join(){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('x-access-token',this.getToken());
return Observable.create(observer =>{
this.http.get('/localhost/chat/'+this.room,{headers : headers})
.map(res => res.json())
.subscribe(
data=>{
observer.next(data);
},
(err) =>{
observer.error(err);
}
);
})
}
My page.ts just use this API cyclically:
join(){
this.myProvider.join().subscribe(
(data)=>{
if(data.success){
... /* doing smt */ ....
this.join();
}else{
this.message=data.message;
//TBD sleep....
//this.join();
}
},
(err) => {
this.message="Connectivity with server Lost...";
});
}
My question is: I would like to write a function in page.ts in order to stop this cycle.
How can I kill a pending get request?
A solution that doesn't work was
I tried to keep a pointer to the observable object in my page.ts:
export class Page {
...
join_channel: any;
join(){
this.join_channel = this.myProvider.join().subscribe(
(data)=>{
...
this.join();
...
Then I by calling the this.join_channel.unsubscribe() I wanted to close the request, so in my case:
ionViewWillLeave() {
this.join_channel.unsubscribe();
delete this;
}
But even by unsubscribing, the get request is still there pending; so when I try to enter again in my page, a new join() can't receive a http.get response at the first step, because the answer will be used before for the previous request which is still pending.
Use timeout from rxjs
this.http.get(API)
.timeout(2000)
.map(res => res.json()).subscribe((data) => {
return data;
},
(err) => {
return err;
}
);
Don't forget to import import 'rxjs/add/operator/timeout';
If you are using angular 6 you have to use
pipe(timeout(2000))
this.http.get(API)
.pipe(timeout(2000))
.map(res => res.json()).subscribe((data) => {
return data;
},
(err) => {
return err;
}
);
I'm trying to make a http request and after that return the response object or bool. Doesn't realy matter which one. But i'm can't catch the error. I have a handleError function, but it does not realy work.
My code now looks like this.
The service
updateProduct(product: Product): Promise<number> {
return this.http.put('/api/products/1' + product.id,product)
.toPromise()
.then(response => response.status)
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
//console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
The save function
onSave(): void {
this.productService.updateProduct(this.product)
.then(() => this.goBack())
.catch(er => console.log(er));
}
How can I get this to work?
What i see you're returning error.message when it exists
return Promise.reject(error.message || error);
Return whole error object instead of just message if you want to manipulate with that.
return Promise.reject(error);