Having trouble figuring out how to handle exceptions here.
Container
async handleResetPassword(uuid) {
console.log("handleResetPassword invoked!")
try {
await this.props.resetUserPassword()
...rest of the code
Thunk Action
export function resetPasssord() {
return async (dispatch, getState) => {
const state = getState()
try {
const response = await UserApi.ResetUserPassword(state.auth.token)
if(response && response.body){
dispatch(UserActions.resetPasswordReceived)
}
dispatch(UserActions.resetPasswordFail)
}
catch(err) {
//what do I do here? How should I resolve this?
dispatch(UserActions.resetPasswordFail)
}
}
}
Also how to handle the related API's catch. As you see above the thunk action calls the Api here:
UserApi (uses superagent):
const ResetUserPassword = async (token) => {
try{
return await request
.post(`${endpoint}/users/recover-password`)
.set({ Authorization: `Bearer ${token}` })
.accept('application/json')
}
catch(err) {
console.log(err)
//how handle this?
}
}
Here are my thoughts when brainstorming this:
need to return some kind of promise from API call in catch so that await can resolve in thunk action
need to return some kind of promise from thunk action in catch so that container can resolve the rejection
do I dispatch an action for error in the try-catch?
but I'm not sure if I'm on the right path or what to code in each of those catches above.
Related
This question already has answers here:
fetch resolves even if 404?
(5 answers)
Closed 8 months ago.
Its basically what the title says, my template string in the URL is formatted directly with user input, and if the user inputs incorrectly, the try/catch should catch it and return a 404 and it should console log "Error" while also returning "Error" on the screen, but it doesn't, the Component passes through anyways
import './cssdirect/App.css';
import Daily from './components/daily';
import {useState, useEffect} from 'react'
import Loading from './Loading';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faHeart } from '#fortawesome/free-solid-svg-icons'
function App() {
const [loading, setLoading] = useState(true)
const [city, setCity] = useState('Paris')
const [weatherData, setWeather] = useState([])
const [error, setError] = useState(false)
const getWeather = async() =>{
setLoading(true)
setError(false)
try{
const resp = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=imperial`)
const pResp = await resp.json()
setWeather(pResp)
setLoading(false)
}
catch(err){
console.log("ERRRRROOOORRRR")
setError(true)
}
}
const handleSubmit = (e) =>{
e.preventDefault()
console.log(city)
getWeather()
}
useEffect(()=>{
getWeather()
},[])
if(loading==true){
return <>
<h1 className='title'>Monkey Wit Da Weather</h1>
<Loading/>
}
else if(error===true){
return<>
<h1>Error</h1>
</>
}
else{
return<>
<h1 className='title'>Monkey Wit Da Weather</h1>
<Daily {...weatherData} setCity={setCity} city={city} getWeather={getWeather} handleSubmit={handleSubmit}/>
</>
}
}
export default App;
Because it is not an error, it will return a Promise. The Promise API proposes the following:
Each asynchronous task will return a promise object.
Each promise object will have a then function that can take two
arguments, a success handler and an error handler.
The success or the error handler in the then function will be called
only once, after the asynchronous task finishes.
The then function will also return a promise, to allow chaining
multiple calls.
Each handler (success or error) can return a value, which will be
passed to the next function as an argument, in the chain of
promises.
If a handler returns a promise (makes another asynchronous request),
then the next handler (success or error) will be called only after
that request is finished.
So it will be :
const resp = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=imperial`).then(response => {
console.log("response:", response)
}).catch(error => {
console.log("error:", error)
})
Per Fetch API:
The Promise returned from fetch() won't reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing.
fetch('https://httpstat.us/404')
.then(function(){
console.log('200 OK');
}).catch(function(){
console.console.log('404');
})
Above code it won't run into catch block.
You can do something like this:
class FetchError extends Error {
constructor(response) {
super(`HTTP error ${response.status}`);
this.response = response;
}
}
function fetchSomething(...args) {
return fetch(...args)
.then(response => {
if (!response.ok) {
throw new FetchError(response);
}
return response;
});
}
You can read more here github/fetch/issues/203
Consider the following Javascript/React code:
// Javascript function that has a fetch call in it.
export const signIn = (email:string, password:string) => {
console.log("FETCHING...");
fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
})
.catch((error) => {
console.error('ERROR: ', error)
})
console.log("DONE FETCHING...");
}
// A functional component that references signIn.
export const SignIn: React.FC<Props> = () => {
// irrelevant code ...
const onSubmit = (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
return <>A form here submits and calls onSubmit</>
}
This produces the following console log output:
SIGNING IN...
FETCHING...
DONE FETCHING...
SIGNED IN...
FETCHED DATA...
I want FETCHED DATA... to show up before DONE FETCHING.... I've tried playing around with aysnc/await but that's not working so I don't know where to go from here.
Just add another .then
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
return
}).then(()=> {
console.log("DONE FETCHING...");
})
.catch((error) => {
console.error('ERROR: ', error)
})
It would have to be in the then statements if you want the console.log to wait until the promise is resolved. Here's an example that uses async/await:
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
const response = await fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
const data = await response.json();
console.log("FETCHED DATA...")
console.log("DONE FETCHING...");
}
You would also need to turn this into an async function if you want the console.log to happen after the data is done fetching:
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
await signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
In order to use async await, you need to return a promise from the call. So basically you don't execute the .then and wrap the call in a try catch block.
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
return fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
}
and
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
try {
const data = await signIn(email, password, setAuthentication, setCurrentUser)
// Parse data, do something with it.
console.log("SIGNED IN...");
} catch (e) {
// handle exception
}
}
You may want to look more into how promises in JavaScript works.
One problem here is in signIn. What you're doing right now is:
function signIn() {
// 1. log FETCHING
// 2. call asynchronous fetch function
// 3. log DONE FETCHING
}
The key here is that fetch is asynchronous. The program doesn't wait for it to finish before moving on. See the problem? The JavaScript interpreter is going to run step 3 without waiting for step 2 to finish.
There are multiple ways to fix this. First, you can use then. Here's an example:
promise
.then(res => func1(res))
.then(res => func2(res))
.then(res => func3(res))
Here, you're telling JavaScript to:
Run promise, and wait for it to resolve.
Take the result from promise and pass it into func1. Wait for func1 to resolve.
Take the result from func1 and pass it into func2. Wait for func2 to resolve.
etc.
The key difference here is that you are running each then block in order, waiting for each previous promise to be resolved before going to the next one. (Whereas before you didn't wait for the promise to resolve).
Your code with promises would look like:
export const signIn = (email: string, password: string) => {
console.log("FETCHING...")
// Note that we return the promise here. You will need this to get onSubmit working.
return fetch(/* args */)
.then(res => res.json())
.then(({ data }) => console.log("DONE FETCHING"))
.catch(err => /* HANDLE ERROR */)
}
The second way to fix this is to use async and await. async and await is simply syntax sugar over promises. What it does underneath is the exact same, so make sure you understand how promises work first. Here's your code with async and await:
// The async keyword here is important (you need it for await)
export const signIn = async (email: string, password: string) => {
console.log("FETCHING...");
try {
const res = await fetch(/* args */) // WAIT for fetch to finish
const { data } = res.json()
console.log("FETCHED DATA...")
} catch (err) {
/* HANDLE ERROR */
}
console.log("DONE FETCHING...")
}
There's also a second similar problem in onSubmit. The idea is the same; I'll let you figure it out yourself (the important part is that you must return a Promise from signIn).
So I am implementing axios call cancelation in the project. Right now looking at axios documentation it seems pretty straight forward https://github.com/axios/axios#cancellation
So I did define variables on the top of my Vue component like
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
obviously on top of that is import axios from 'axios';
Then I have a method of fetching the API
On the top of the method I want to cancel out the request in case it is running so the last one cancels out if the user spams the filtering.
async fetchPartners(inputToClear) {
source.cancel();
...
try {
const response = await axios.get(`../partners?limit=1000${this.createRequestString()}`, {
cancelToken: source.token
});
// Here you can see I did add the cancelToken to the request
this.partners = response.data.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
}
const fetchErrors = this.utilGlobalHandleErrorMessages(error);
this.utilGlobalDisplayMessage(fetchErrors.message, { type: 'error' });
return [];
} finally {
...
}
},
So it is pretty straight forward, just took the code from axios documentation I gave you above, it should be working by logic. But what is actually happening, it doesn't even allow me to fetch the call, it is already cancelled out before I can call it. On console it shows me
Request canceled undefined
It just catches the error as if I am cancelling the call, but how can it be, because I am source.cancel() before the call.
Anyone has any idea?
I hope you should throttle your requests instead of canceling the request.
Could you please try the following if throttle does not suit your requirement?
const CancelToken = axios.CancelToken;
let source;
async fetchPartners(inputToClear) {
if(source){
source.cancel();
}
...
source = CancelToken.source();
try {
const response = await axios.get(`../partners?limit=1000${this.createRequestString()}`, {
cancelToken: source.token
});
// Here you can see I did add the cancelToken to the request
this.partners = response.data.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
}
const fetchErrors = this.utilGlobalHandleErrorMessages(error);
this.utilGlobalDisplayMessage(fetchErrors.message, {
type: 'error'
});
return [];
} finally {
...
}
}
In axios, why throw new Error() is not allowed inside catch()?
I am in such requirement, where if an error is returned by the server, the catch block should throw an error, which later on will be handled by redux-saga and appropriate action would be dispatched.
The API call:
export const userSignupApi = userDetails => {
const endpoint = `${URL_ROOT}${URN_SIGNUP}`;
axios
.post(endpoint, userDetails)
.then(response => {
console.log("Response: ", response);
//do something with the response
})
.catch(error => {
throw new Error(error.response.data.message);
});
};
I am getting Unhandled Rejection (Error) because of the above catch block.
Below is my saga which handles the operation:
import { call, takeLatest, put } from "redux-saga/effects";
function* handleUserSignup(action) {
try {
yield call(userSignupApi, action.userDetails);
yield put(userSignupSuccess()); //dispatching success action
} catch (error) {
yield put(userSignupFail(error)); //dispatching error action
}
}
function* watchUserSignup() {
yield takeLatest(NEW_USER.SIGNUP, handleUserSignup);
}
Edit: Why I want the above code structure? Because this makes easy to unit test the API code and the saga code.
The Promise created in userSignupAPI isn't being used anywhere (it's not even being returned), so when an error is thrown inside catch, the Promise chain resolves to an (uncaught) rejected Promise, resulting in the error.
In the caller of userSignupAPI, you should await the call, so that the try/catch inside handleUserSignup will see the thrown error:
export const userSignupAPI = userDetails => {
const endpoint = `${URL_ROOT}${URN_SIGNUP}`;
return axios
.post(endpoint, userDetails)
.then(response => {
console.log("Response: ", response);
})
.catch(error => {
throw new Error(error.response.data.message);
});
};
async function* handleUserSignup(action) {
try {
yield await call(userSignupApi, action.userDetails);
yield put(userSignupSuccess()); //dispatching success action
} catch (error) {
yield put(userSignupFail(error)); //dispatching error action
}
}
function* watchUserSignup() {
yield takeLatest(NEW_USER.SIGNUP, handleUserSignup);
}
(make sure that call returns the Promise returned by userSignupApi)
I got this working. I was doing it completely wrong. As suggested by #certianPerformance and after reading some questions and github issues, I got the right way to handle this. Instead of returning the api response, I should have returned the promise.
Here is the solution:
export const userSignupApi = userDetails => {
const endpoint = `${URL_ROOT}${URN_SIGNUP}`;
return axios.post(endpoint, userDetails);
};
Saga:
import { call, takeLatest, put } from "redux-saga/effects";
function* handleUserSignup(action) {
try {
const response = yield call(userSignupApi, action.userDetails);
response.then(response => {
const location = response.headers.location;
put(userSignupSuccess(location));
put(newUserWelcomeNote("Welcome user"));
});
} catch (error) {
const errorMessage = error.response.data.message;
yield put(userSignupFail(errorMessage));
}
}
function* watchUserSignup() {
yield takeLatest(NEW_USER.SIGNUP, handleUserSignup);
}
You are using try catch because you don't want to throw error in browser console you want to handle in catch. Throwing error in catch will remove its purpose.If you want to throw error remove try catch(which is not a recommended way)
UPDATE
For axios catch method catches any error thrown by api url. If you don't want to catch error and show it in browser, you can remove catch block or you can call the action which handles error. For more information about promise catch you can refer here
I understand vuex actions return promises, but I haven't found the ideal pattern to handle errors in vuex. My current approach is to use an error interceptor on my axios plugin, then committing the error to my vuex store.
in plugins/axios.js:
export default function({ $axios, store }) {
$axios.onError(error => {
store.dispatch('setError', error.response.data.code);
});
}
in store/index.js:
export const state = () => ({
error: null,
});
export const mutations = {
SET_ERROR(state, payload) {
state.error = payload;
},
}
export const actions = {
setError({ commit }, payload) {
commit('SET_ERROR', payload);
},
};
I would then use an error component watching the error state and show if there is an error. Thus there is really no need to catch any errors in either my action or in the component that dispatched the action. However I can't help to worry if it's bad design leaving exceptions uncaught? What issues could I encounter if I handle errors by this design? Suggestions on any better ways to do this?
I would argue that you should make the API call in the vuex action and if it fails, reject the promise with the error from the API call. I would avoid listing to all Axios errors and instead handle the error when it is returned in the promise, in my opinion, it would be easier to maintain and debug this way
For example:
getCartItems: function ({commit}, url) {
return axios.get(url).then(response => {
commit('setCartItems', response.data)
return response
}).catch(error => {
throw error
})
},
Improved above example to avoid redundant promise wrapping and use async/await for simplified code:
export const getCartItems = async ({commit}, url) => {
const response = await axios.get(url);
commit('setCartItems', response.data)
return response;
};