does catch statement in promise catch nested errors [duplicate] - javascript

I have a bit of a long login process that relies on 3 api calls that looks like this at the moment:
export const authenticationSignIn = (email, password) =>
(dispatch) => {
dispatch({ type: AUTHENTICATION_REQUEST });
apiAccountStatus(email, password)
.then(({ data }) => {
const status = data.status;
if (status === 'ACCOUNT_CREATED') {
apiSignIn(email, password)
.then(({ data: sessionData }) => {
apiIndexAccounts()
.then(({ data: accountsData }) => {
dispatch({ type: AUTHENTICATION_SUCCESS });
window.router.transitionTo('/dashboard/home');
});
});
} else if (status === 'SOMETHING ELSE') {
// TODO: HANDLE SOMETHING ELSE
}
})
.catch(({ response }) => {
dispatch({ type: AUTHENTICATION_FAILURE });
dispatch(notificationShow('ERROR', response.data.type));
});
};
As you can see this function is quiet verbose, but each nested api call relies on data returned from previous and I am trying to clean this up as much as possible (dispatch bits are redux specific, but these essentially fire whatever is passed in). At the end you will see a catch statement, my question is will this catch statement work for all of the promisses or only apiAccountStatus?

At the end you will see a catch statement, my question is will this catch statement work for all of the promises?
No, it only works for the outer promise, the one returned by the then call. This needs to be rejected for the catch callback to be activated. To get this promise rejected, either apiAccountStatus(…) must reject or the then callback must throw an exception or return a promise that will be rejected.
This last thing is what you were missing - you were creating more promises inside that then callback, but you weren't returning them so that they will not chain. You have to do
export function authenticationSignIn(email, password) {
return (dispatch) => {
dispatch({ type: AUTHENTICATION_REQUEST });
apiAccountStatus(email, password)
.then(({data: {status}}) => {
if (status === 'ACCOUNT_CREATED') {
return apiSignIn(email, password)
// ^^^^^^
.then(({ data: sessionData }) => {
return apiIndexAccounts()
// ^^^^^^
.then(({ data: accountsData }) => {
dispatch({ type: AUTHENTICATION_SUCCESS });
window.router.transitionTo('/dashboard/home');
});
});
} else if (status === 'SOMETHING ELSE') {
// TODO: HANDLE SOMETHING ELSE
}
})
.catch(({ response }) => {
dispatch({ type: AUTHENTICATION_FAILURE });
dispatch(notificationShow('ERROR', response.data.type));
});
};
}

Related

Fetch API POST in React app not logging errors in catch block from express/node server [duplicate]

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

React - API Call returning correct result but not passing along

I'm performing an API call to Bing Web Search API and running into an error with the response.
Here's the code:
await webSearchApiClient.web.search(searchText).then(result => {
console.log('Results API', result)
return result
}).catch((err) => {
throw err;
})
The issue I'm running into is that the result does come back (the console log 'Results API' prints the expected return values), but the return statement isn't passing the value along. The rest of the code is written to be asynchronous, and when I print the values in the code calling the API function I get this:
Line 1: Results API {"_type": "SearchResponse","queryContext": {"originalQuery":...
Line 2: Returned Results undefined
I've tried setting the result to other variables with no success
I'm using redux as well, here's the code for the dispatch call and the code in the redux action (the second console log is the the redux actions):
const onSearchResults = async () => {
dispatch(getWebResults(searchText))
dispatch(getImageResults(searchText))
}
export const getWebResults = (searchText) => {
return async dispatch => {
const onStart = () => {
dispatch({ type: GET_WEB_RESULTS_STARTED });
}
const onSuccess = (response) => {
dispatch({ type: GET_WEB_RESULTS_SUCCESS, payload: response });
return response;
}
const onError = (error) => {
dispatch({ type: GET_WEB_RESULTS_FAILURE, payload: error });
return error;
}
try {
onStart();
const webResults = await BingWebSearchApi(searchText);
console.log('Returned Results', webResults)
return onSuccess(webResults)
} catch(error) {
return onError(error)
}
}
}
Instead of doing this
await webSearchApiClient.web.search(searchText).then(result => {
console.log('Results API', result)
return result
}).catch((err) => {
throw err;
})
Since the return statement is for the then function scope, you should return the promise like this
return webSearchApiClient.web.search(searchText);
And then in your redux actions do something like
(...)
try {
onStart();
BingWebSearchApi(searchText).then((webResults)=> {
console.log('Returned Results', webResults);
onSuccess(webResults);
});
} catch(error) {
return onError(error)
}

Chaining promises with action reducers in React

I have a method which is firing two http requests to API to get the data.
getServerDetails(this.props.match.params.id) //1
.then(res => {
this.props.getServerDetailsAction({ success: true, data: res.data })
if (!_.isEmpty(res.data)) {
return getServerScomAlerts(res.data.ServerName) //2
}
})
.catch((err) => { //3
this.props.getServerDetailsAction({ success: false, error: err })
})
.then(res => {
if (!_.isEmpty(res)) {
this.props.getServerScomAlertsAction({ success: true, data: res.data })
}
})
.catch((err) => { //4
this.props.getServerScomAlertsAction({ success: false, error: err })
})
getServerDetails (1) and getServerScomAlerts (2) methods are returning promises. getServerScomAlerts (2) is dependent on the result of getServerDetails (1)
The problem is that if error occurs in getServerScomAlerts (2) the first catch block (3) is executed. Is there a way how to get the data from the first method (1) (without accessing store) and at the same time jump to the corresponding catch block (4) if the error occurs?
Something like this might give you an idea how to achieve your goal. Instead of having nested then, you can use async/await and it waits for the result of each call before proceeding to the next:
export const actionCreators = {
myFunc: (param1, param2) => async (dispatch, getState) => {
try {
const response1 = await fetch(param1);
const data1 = await response.json();
const response2 = await fetch(param2);
const data2 = await response2.json();
} catch (err) {
console.log(err);
}
}
}
It is really interesting with your problem since you want to do multiple actions which are slightly different from each other only :). I would rewrite as following
const getServerScomAlertsAction = (success, props) => (res) => {
success = success && !_.isEmpty(res);
props.getServerScomAlertsAction(success ? { success, data: res.data } : { success, error: res });
};
const getServerDetailsAction = (success, props) => (res) => {
props
.getServerDetailsAction(success ? { success, data: res.data } : { success, error: res })
.then(getServerScomAlertsAction(true, props))
.catch(getServerScomAlertsAction(false, props));
};
getServerDetails(this.props.match.params.id)
.then(getServerDetailsAction(true, this.props))
.catch(getServerDetailsAction(false, this.props));
This is a good use case for .finally() method, currently in draft specification according to the MDN. It allows you to remove duplication logic, by running the method after the promise is resolved or rejected.
As shown in the MDN example:
let isLoading = true;
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); /* this line can also throw, e.g. when console = {} */ })
.finally(function() { isLoading = false; });
If you want to use the feature right now, you can consider using Bluebird for your promise implimentation, which in some cases is faster, and should be easy to upgrade from as the method is standarized

Pass error after catch on promises

Hi I'm new so sorry if my question does not formulate properly.
I want to define a promise from axios js in a global function.
Here I want to handle / catch the 401 status globally and logout the user.
I do not want to handle it in every single query.
Here my source global function to handle a request:
export function requestData (url, payload = {}) {
return axios.post(url, payload)
.then(response => {
return response.data
})
.catch(error => {
if (error.response.status === 401) {
logout()
} else {
return error
}
})
}
And here a example function I use on a controller:
requestData('/api/persons', {options: this.options, search: search})
.then(data => {
this.data = data
})
.catch(error => {
this.error = error.toString()
})
My Problem is that the promise catch in my controller will not fire when there is an exception. How to realize this?
change return error in your requestData function to throw error
As per the Axios docs
You can intercept requests or responses before they are handled by then or catch.
You're going to want to use the Response Interceptor:
axios.interceptors.response.use(function(response) {
// Do something with response data
return response;
}, function(error) {
// Do something with response error
if (error.status === 401) {
logout()
}
return Promise.reject(error);
});
Replacing return error by throw error is the half work.
When I'm right the throw error in promise catch will not invoke the next promise .catch statement. This will work in the .then statement.
This way it should work:
export function requestData (url, payload = {}) {
return axios.post(url, payload)
.then(response => {
return response.data
})
.catch(error => {
if (error.response.status === 401) {
logout()
} else {
return error
}
})
.then(result => {
if (result instanceof Error) {
throw result
} else {
return result
}
})
}

Will outermost .catch() work for all chained / nested promisses?

I have a bit of a long login process that relies on 3 api calls that looks like this at the moment:
export const authenticationSignIn = (email, password) =>
(dispatch) => {
dispatch({ type: AUTHENTICATION_REQUEST });
apiAccountStatus(email, password)
.then(({ data }) => {
const status = data.status;
if (status === 'ACCOUNT_CREATED') {
apiSignIn(email, password)
.then(({ data: sessionData }) => {
apiIndexAccounts()
.then(({ data: accountsData }) => {
dispatch({ type: AUTHENTICATION_SUCCESS });
window.router.transitionTo('/dashboard/home');
});
});
} else if (status === 'SOMETHING ELSE') {
// TODO: HANDLE SOMETHING ELSE
}
})
.catch(({ response }) => {
dispatch({ type: AUTHENTICATION_FAILURE });
dispatch(notificationShow('ERROR', response.data.type));
});
};
As you can see this function is quiet verbose, but each nested api call relies on data returned from previous and I am trying to clean this up as much as possible (dispatch bits are redux specific, but these essentially fire whatever is passed in). At the end you will see a catch statement, my question is will this catch statement work for all of the promisses or only apiAccountStatus?
At the end you will see a catch statement, my question is will this catch statement work for all of the promises?
No, it only works for the outer promise, the one returned by the then call. This needs to be rejected for the catch callback to be activated. To get this promise rejected, either apiAccountStatus(…) must reject or the then callback must throw an exception or return a promise that will be rejected.
This last thing is what you were missing - you were creating more promises inside that then callback, but you weren't returning them so that they will not chain. You have to do
export function authenticationSignIn(email, password) {
return (dispatch) => {
dispatch({ type: AUTHENTICATION_REQUEST });
apiAccountStatus(email, password)
.then(({data: {status}}) => {
if (status === 'ACCOUNT_CREATED') {
return apiSignIn(email, password)
// ^^^^^^
.then(({ data: sessionData }) => {
return apiIndexAccounts()
// ^^^^^^
.then(({ data: accountsData }) => {
dispatch({ type: AUTHENTICATION_SUCCESS });
window.router.transitionTo('/dashboard/home');
});
});
} else if (status === 'SOMETHING ELSE') {
// TODO: HANDLE SOMETHING ELSE
}
})
.catch(({ response }) => {
dispatch({ type: AUTHENTICATION_FAILURE });
dispatch(notificationShow('ERROR', response.data.type));
});
};
}

Categories

Resources