I want to show an error message when my API fetch throws an error but this error actually gets fulfilled so in the extra reducers, the rejected part doesn't get invoked at all.
export const searchWeather = createAsyncThunk(
"weather/searchWeather",
async (apiAddress) => {
const response = await fetch(apiAddress);
const data = await response.json();
return data;
}
);
................................................................................
extraReducers: (builder) => {
builder
.addCase(searchWeather.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(searchWeather.fulfilled, (state, action) => {
state.weather = action.payload;
state.isLoading = false;
state.hasError = false;
})
.addCase(searchWeather.rejected, (state) => {
state.isLoading = false;
state.hasError = true;
});
},
In this way even if I get a 404 Error, it still does get fulfilled and not rejected.
What I did was to include Promise.reject() in my async function in this way:
export const searchWeather = createAsyncThunk(
"weather/searchWeather",
async (apiAddress) => {
const response = await fetch(apiAddress);
if (!response.ok) {
return Promise.reject();
}
const data = await response.json();
return data;
}
);
And it indeed does work without any problems and shows the error message I've defined somewhere else.
But I want to know if this is actually the right way to do it or if there is a better solution to this.
This is pretty much the correct way - fetch does not throw on a non-2xx-status code.
Sementics can change a bit - you would usually either throw or return rejectWithValue:
if (!response.ok) {
throw new Error("response was not ok")
}
or
if (!response.ok) {
return thunkApi.rejectWithValue("response was not okay")
}
Related
I have this test I made just to check an API, but then i tryied to add an URL from a second fetch using as parameter a value obtained in the first fetch and then return a value to add in the first fecth. The idea is to add the image URL to the link. thanks in advance.
function script() {
const url = 'https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20'
const result = fetch(url)
.then( (res)=>{
if(res.ok) {
return res.json()
} else {
console.log("Error!!")
}
}).then( data => {
console.log(data)
const main = document.getElementById('main');
main.innerHTML=`<p><a href='${data.next}'>Next</a></p>`;
for(let i=0; i<data.results.length;i++){
main.innerHTML=main.innerHTML+`<p><a href=${getImageURL(data.results[i].url)}>${data.results[i].name}</a></p>`;
}
})
}
async function getImageURL(imgUrl) {
const resultImg = await fetch(imgUrl)
.then( (res)=> {
return res.json()
})
.then (data => {
console.log(data.sprites.other.dream_world.front_default);
})
return resultImg.sprites.other.dream_world.front_default;
}
In general, don't mix .then/.catch handlers with async/await. There's usually no need, and it can trip you up like this.
The problem is that your fulfillment handler (the .then callback) doesn't return anything, so the promise it creates is fulfilled with undefined.
You could return data, but really just don't use .then/.catch at all:
async function getImageURL(imgUrl) {
const res = await fetch(imgUrl);
if (!res.ok) {
throw new Error(`HTTP error ${res.status}`);
}
const resultImg = await res.json();
return resultImg.sprites.other.dream_world.front_default;
}
[Note I added a check of res.ok. This is (IMHO) a footgun in the fetch API, it doesn't reject its promise on HTTP errors (like 404 or 500), only on network errors. You have to check explicitly for HTTP errors. (I wrote it up on my anemic old blog here.)]
There's also a problem where you use getImageURL:
// Incorrent
for (let i = 0; i < data.results.length; i++) {
main.innerHTML=main.innerHTML+`<p><a href=${getImageURL(data.results[i].url)}>${data.results[i].name}</a></p>`;
}
The problen here is that getImageURL, like all async functions, returns a promise. You're trying to use it as those it returned the fulfillment value you're expecting, but it can't — it doesn't have that value yet.
Instead, you need to wait for the promise(s) youre creating in that loop to be fulfilled. Since that loop is in synchronous code (not an async function), we'd go back to .then/.catch, and since we want to wait for a group of things to finish that can be done in parallel, we'd do that with Promise.all:
// ...
const main = document.getElementById('main');
const html = `<p><a href='${data.next}'>Next</a></p>`;
Promise.all(data.results.map(async ({url, name}) => {
const realUrl = await getImageURL(url);
return `<p><a href=${realUrl}>${name}</a></p>`;
}))
.then(paragraphs => {
html += paragraphs.join("");
main.innerHTML = html;
})
.catch(error => {
// ...handle/report error...
});
For one, your
.then (data => {
console.log(//...
at the end of the promise chain returns undefined. Just remove it, and if you want to console.log it, do console.log(resultImg) in the next statement/next line, after await.
This the final version that accomplish my goal. Just want to leave this just in case someone finds it usefull. Thanks for those who answer!
function script() {
const url = 'https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20'
const result = fetch(url)
.then( (res)=>{
if(res.ok) {
return res.json()
} else {
console.log("Error!!")
}
}).then( data => {
console.log(data)
const main = document.getElementById('main');
main.innerHTML=`<p><a href='${data.next}'>Proxima Página</a></p>`;
Promise.all(data.results.map(async ({url, name}) => {
const realUrl = await getImageURL(url);
return `<div><a href=${realUrl}>${name}</a></div>`;
}))
.then(paragraphs => {
main.innerHTML=main.innerHTML+paragraphs;
})
.catch(error => {
console.log(error);
});
})
}
async function getImageURL(imgUrl) {
const res = await fetch(imgUrl);
if(!res.ok) {
throw new Error(`HTTP Error ${res.status}`)
}
const resultImg = await res.json();
return resultImg.sprites.other.dream_world.front_default
}
Say for example when a login function calls an API and it returns an error because of something like invalid credentials. I have noticed that it still goes through the fulfilled case in the extra reducers part. Should I add an if statement to check if response code is 200 or is there a way for the thunk to go through the rejected case?
extraReducers: builder => {
builder.addCase(login.pending, (state, action) => {
state.fetchingError = null;
state.fetchingUser = true;
});
builder.addCase(login.fulfilled, (state, {payload}) => {
console.log(payload, 'hello?');
state.user = payload.data.user;
});
builder.addCase(login.rejected, (state, action) => {
state.fetchingUser = false;
state.fetchingError = action.error;
});
},
You can use rejectWithValue in createAsyncThunk to customize the reject action.
It also takes an argument which will be "action.payload" in the reject action.
In createAsyncThunk:
const updateUser = createAsyncThunk(
'users/update',
async (userData, { rejectWithValue }) => {
const { id, ...fields } = userData
try {
const response = await userAPI.updateById(id, fields)
return response.data.user
} catch (err) {
// Use `err.response.data` as `action.payload` for a `rejected` action,
// by explicitly returning it using the `rejectWithValue()` utility
return rejectWithValue(err.response.data)
}
}
)
https://redux-toolkit.js.org/api/createAsyncThunk#handling-thunk-errors
I am trying to return a web3 value rather than a promise in order for me to call the getId method.
my return value for web3 hasn't been resolved thus is still a promise hence Im unable to call getId method on it
Can any experienced redux user point out to me what is wrong with my below code? thanks
In actions.js
export const web3Loaded = (eth) => {
const web3 = new Web3(eth);
return async (dispatch) => {
dispatch({
type: WEB3_LOADED,
connection: web3,
});
return web3; // it remains a promise
};
};
In App.jsx
useEffect(() => {
const loadBlockchain = async () => {
if (window.ethereum) {
try {
await window.ethereum.enable();
const web3 = web3Loaded(window.ethereum);
console.log("web3", web3); // it returns a promise hence I'm unable to call the getId method
const networkId = await web3.eth.net.getId();
console.log("networkId", networkId);
} catch (error) {
console.log(
"Non-Ethereum browser detected. You should consider trying MetaMask!"
);
}
}
};
loadBlockchain();
}, []);
Just add an await when calling web3Loaded will solve the issue
useEffect(() => {
const loadBlockchain = async () => {
if (window.ethereum) {
try {
await window.ethereum.enable();
const web3 = await web3Loaded(window.ethereum);
console.log("web3", web3); // it returns a promise hence I'm unable to call the getId method
const networkId = await web3.eth.net.getId();
console.log("networkId", networkId);
} catch (error) {
console.log(
"Non-Ethereum browser detected. You should consider trying MetaMask!"
);
}
}
};
loadBlockchain();
}, []);
In this app, I'm using async await to try handle an API call through a couple of functions. As I'm testing the sad path error routing, I'm noticing that the error does not end up in the catch.
The Action which starts the whole process
export const submitBasicDetails = form => async dispatch => {
try {
const response = await interstitialHandlerPromise(Actions.SUBMIT_FORM, form); // <- we end up here
console.log('submitBasicDetails response', response); // <-- we see the error here
const {id} = response.data;
dispatch({
type: Actions.SUBMIT_FORM,
id
});
return response;
} catch (error) {
console.log('ACTION', error); // <-- instead of here :()
dispatch({
type: Actions.SUBMIT_FORM_FAIL,
id: null,
error: 'There was a server error, please try again later.'
});
}
};
The form data then hits this interstitialHandlerPromise where we decide which API method to use:
export const interstitialHandlerPromise = (type, data) => {
const {requestMethod, config} = determineRequestMethod(type);
const requests = requestMethod(data);
return new Promise((resolve, reject) =>
interstitialHandler(requests, config)
.then(response => resolve(response))
.catch(error => {
console.log('intersitial error', error);
reject(error);
})
);
};
Finally the postForm function which is requests that is inside of the function above:
// /test will cause a 500 error since it doesn't exist
export const postForm = async (form, END_POINT = '/api/test') => {
const resPayload = form.data;
const resUrl = `${BASE_URL}${V1}${END_POINT}`;
try {
const res = await axios.post(resUrl, {...resPayload});
return {
res
};
} catch (e) {
console.log('postForm e', e); // <-- This hits because `/test` returns 500
return new Error(e.message);
}
};
Got it! Instead of returning the error, I needed to throw the error.
In the postForm function:
} catch (e) {
console.log('postInitialLoss e', e);
throw new Error(e.message);
}
Then in the intersitialHandlerPromise:
export const interstitialHandlerPromise = async (type, data) => {
const {requestMethod, config} = determineRequestMethod(type);
const requests = requestMethod(data);
try {
const response = await interstitialHandler(requests, config);
return response;
} catch(e) {
throw new Error(e); // <- this gets hit
}
};
Now finally back in the action we will end up in the catch:
} catch (error) {
console.log('ACTION (SHOULD GET HERE)!', error); // <-- WE are here! :D
dispatch({
type: Actions.SUBMIT_BASIC_DETAILS_FAIL,
initialLossCreated: false,
error: 'There was a server error, please try again later.'
});
}
api/index.js
const URL = 'http://10.0.2.2:5000';
const fetching = false;
export default (type, filter, dateFilter, position) => {
if(fetching) return Promise.reject(new Error('Request in progress'));
fetching = true;
return fetch(URL + `/search/${type}/${filter}/${dateFilter}/${position}/0/0`)
.then(response => Promise.all([response, response.json()]))
.catch(err => console.log("error catch search:", err.message))
}
I need to put fetching false so in that way i can call this function again, but i dont know where to put it to make it work.
If i create another then like this after the then writen and before the catch like this:
.then(() => fetching = false)
The problem is that it returns false to the place the function is called and NOT the data this is where is called:
actions/index.js
getDataApi(type, filter, dateFilter, position)
.then(res => {
if (res !== false) {
if (state.dataReducer.data.length === 0) {
dispatch(getDataSuccess(res[1]))
} else {
dispatch(getDataSuccess(res[1], state.dataReducer.data))
}
}
})
.catch((err) => console.log(9999, err))
So my problem is that it doesnt enter in the getDataSuccess because is false.I dont know why it cannot send the data to this function and it ends sending the result of fetching = false.
You need another .then so that you can reassign fetching after the response.json() resolves. You should also probably reassign fetching in the catch so that future requests are possible even if there's an error once, and return false in the catch so the .then after getDataAPI will properly ignore failed requests. Also, use let rather than const for fetching:
const URL = 'http://10.0.2.2:5000';
let fetching = false;
export default (type, filter, dateFilter, position) => {
if (fetching) return Promise.reject('Request in progress');
fetching = true;
return fetch(URL + `/search/${type}/${filter}/${dateFilter}/${position}/0/0`)
.then(response => Promise.all([response, response.json()]))
.then(([response, responseObj]) => {
fetching = false;
return [response, responseObj];
})
.catch(err => {
console.log("error catch search:", err.message);
fetching = false;
// Choose one, depends what you need.
return false; // If you want to ignore the error and do something in a chained .then()
return Promise.reject(err); // If you want to handle the error in a chained .catch()
})
}