I've got an async call of API where the second .catch block is not being executed.
Fist I defined main API call function in utils file like so:
const getCustomer= (body) => {
return new Promise(resolve => {
fetch('/getCustomer', {
method: 'post',
body: JSON.stringify(body),
headers: {
Accept: "application/json"
Content-type: "application/json"
- },
})
.then(response => response.json())
.then(json => resolve(json))
.catch(err => console.log('error happened', err))
})
};
Later on in my JSX file I call the API by importing the function above.
getCustomer(myPayload).then(res => {
setCustomer(res)
}).catch(err => {
setShowError(true)
})
What I am trying to do is to show the error message with setShowError, but for some reason I can only see the console.log('error happened', err) throwing out from utils folder, where I define my fetch function.
Any idea on how I can fix this behavior and execute catch function
If you want both .catches to execute, you'll need to throw inside the first, otherwise the result will be that getCustomer will always return a Promise that resolves. You'll also need to use the reject parameter to the Promise constructor.
new Promise((resolve, reject) => {
// ...
.catch(err => {
console.log('error happened', err);
reject(err);
})
and make sure to return the Promise call in your React code:
return setCustomer(res);
While this is possible, usually a better approach, if feasible, is to not catch the error below, and instead let the caller handle it. Also don't use the explicit Promise construction antipattern.
const getCustomer= (body) => {
return fetch('/getCustomer', {
method: 'post',
body: JSON.stringify(body),
headers: {
Accept: "application/json"
Content-type: "application/json"
- },
})
.then(response => response.json());
};
is what I'd prefer. Unless you really need the setCustomer itself to handle it too, just let the error percolate up to its caller.
Related
I have a script that updates the content of an API. There are several, so the only way is to call repeatedly to the endpoint to get the content of each API. When I am already inside the promise all, and I have the array of promises, what I do is iterating and updating the content that I have obtained.
Although the code seems to be working with an API only, I don't know what is the ideal way to make requests with Axios or fetch inside a promise.all or if just making the request is enough. Think that if for example there are 500 sequences, it will iterate through them and update their content.
The code is:
let promises = [];
data.sequences.forEach((sequence) => {
promises.push(axios.get(
`https://${conf.server.hostname}:${conf.server.port}/resource/${conf.version}/${sequence}`, {
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer bbbbbbb'
}
}
))
});
Promise.all(promises).
then((sequences) => {
//for each sequence to update
for (let i = 0; i < sequences.length; i++) {
let indexPromise = sequences[i];
axios.put(
`https://${conf.server.hostname}:${conf.server.port}/resource/${conf.version}/${sequenceName}`,
indexPromise.data, {
headers: {
'Content-Type': 'application/json'
}
}
).then((response) => {
logger.debug(`Updating content : ${sequenceName}`);
}).catch((err) => {
logger.error(`Error updating content`, err);
})
}
return callback(null, data)
}).catch((err) => {
logger.error(`Error getting content:`, err);
});
I would suggest this structure that makes the .get() and .put() into a combined operation and then runs Promise.all() once on the combined operations:
function someFunction() {
const corePath = `https://${conf.server.hostname}:${conf.server.port}/resource/${conf.version}`;
// return a promise that indicates when we're all done or had an error
return Promise.all(data.sequences.map(sequence => {
return axios.get(`${corePath}/${sequence}`, {
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer bbbbbbb'
}
}).then(data => {
return axios.put(`${corePath}/${sequenceName}`, data, {
headers: {
'Content-Type': 'application/json'
}
});
}).then(() => {
logger.debug(`Updating content : ${sequenceName}`);
}).catch(err => {
logger.error(`Error updating content`, err);
throw err;
});
});
}
Other notes:
Don't mix plain callbacks and promises. If you need to communicate back to some other code when this is done or has an error, then return your promise - don't use a callback.
You don't show where sequenceName comes from. Your debug output makes it seem like it's something that varies by request, but it isn't defined anywhere in the code in your question.
If you want the promise this is returning resolve with some data, then return that value from the final .then(). Your question shows you calling a callback and passing it data, but doesn't show where that comes from.
I have this route set up on my server with nodejs/express:
const testSync = (req, res) => {
//bookingLink and requestOptions defined here
fetch(bookingLink , requestOptions)
.then(response => response.text())
.then(result => {
res.sendStatus(200)
})
.catch(error => {
console.log('error', error)
res.sendStatus(404)
});
}
router.post('/test-sync', testSync);
And here is my client side call :
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({url: `${bookingLink}`})
}
fetch(`${protocol}//${domainName}/api/test-sync`, requestOptions)
.then(response => response.text())
.then(result => {
setSyncTest("success")
})
.catch(error => {
setSyncTest("fail")
});
The point of the client side call is to change the state of syncTest hook to "fail" if the link provided to the endpoint is bad. Now, if the link is bad it shows me the 404 error code in the console, but the syncTest doesn't change. I think it doesn't throw the error. How should I do to throw the error in order to change the hook state ?
Update your function to:
const testSync = (req, res) => {
//bookingLink and requestOptions defined here
return fetch(bookingLink , requestOptions)
.then(response => response.text())
.then(result => {
res.sendStatus(200)
})
.catch(error => {
console.log('error', error)
res.sendStatus(404)
});
}
router.post('/test-sync', testSync);
You need to return your fetch request, which is a Promise. What is happening now is that your function is returning right away before fetch is complete.
or more direct/cleaner layout:
const testSync = (req, res) => fetch(bookingLink , requestOptions)
.then(response => response.text())
.then(result => res.sendStatus(200))
.catch(error => {
console.log('error', error)
res.sendStatus(404)
});
router.post('/test-sync', testSync);
Client
Your client code is not handling the 404 correctly. Based on the instructions at MDN an HTTP 404 or even a 500 will not reject the promise. You will need to handle them in the then().
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.
Im returning fetch function from separate callApi layer, and calling then + catch for it.
Callapi.js
export const callApi = (url, options = {}, headers = {}) => {
const defaultOptions = {
headers: {
'Content-Type': 'application/json',
...headers,
},
...options,
};
return fetch(url, defaultOptions)
.then(res => {
if (!res.ok) {
throw Error(res.statusText);
}
if(res.status === 401)
return res;
})
.catch(error => {
console.log(error)
})
}
export default callApi;
Component.js
const handleSubmit = (e) => {
e.preventDefault();
console.log(loginInfo)
callApi('http://localhost:8080/login',{
method: 'POST',
body: JSON.stringify({
username: loginInfo.email,
password: loginInfo.password,
}),
})
.then((res => {
console.log(res)
setRedirect(true)
}
)
.catch((err) => setError(err)))
}
Im having a hard time understanding why catch block doesn't fire, when I throw new error, but then block with undefined response fires always.
Thanks for your help
It looks like it's to do with this block:
.catch(error => {
console.log(error)
})
As the error is caught it's considered handled and therefor will pass through to the next .then() in handleSubmit
If logging the error is essential in callApi you could consider re-throwing the error
.catch(error => {
console.log(error)
throw error
})
What i can see from your code is that if response is correct, you are returning "res", but in catch you are not returning or throwing anything which lead promise to pass in .then()
.catch(error => {
throw new Error(error)
})
The problem is that .catch() in Callapi.js will catch all errors thrown from prior .then() in the same file. Therefore, no errors will happen in Component.js.
Try either re-throwing error, as Ross Mackay suggested, or moving the .catch() block to Component.js, — whatever suites you the best.
What is the way to take data from getUserConnectRequestData function and pass it to getUserConnectResponseData function ?
as you can see so i try to use then and responseData to for save the data of the getUserConnectRequestData function and than i try pass it into the getUserConnectResponseData function but itd not works .
getUserConnectRequestData().then(() => {
responseData();
});
and this is getUserConnectResponseData function that i want to pass the data from getUserConnectRequestData
export const getUserConnectResponseData = (responseData) => {
return new Promise((resolve, reject) => {
// console.log('THIS IS MY RESPONSE ==============>>>>>>>>>>>', responseData);
try {
fetch(
'https://hghghgghghg3223223',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
Req_Type: responseData.Req_Type,
Language_Code: responseData.Language_Code,
User_ID: responseData.User_ID,
Session_ID: responseData.Session_ID,
Session_Key: responseData.Session_Key,
Client_Type: responseData.Client_Type,
Req_Data: {
Bridge_ID: responseData.Bridge_ID,
},
}),
}
)
.then((response) => response.json())
.then((jsonResponse) => {
resolve(jsonResponse);
});
} catch (error) {
reject(error);
}
});
};
You need to accept the parameter and use it, and call the right function:
getUserConnectRequestData().then((responseData) => {
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^
getUserConnectResponseData(responseData);
// −^^^^^^^^^^^^^^^^^^^^^^^^^^−^^^^^^^^^^^^
});
But since getUserConnectResponseData takes just that one parameter you know that the then callback will only be called with that one single argument:
getUserConnectRequestData().then(getUserConnectResponseData);
You also need to handle errors, so:
getUserConnectRequestData()
.then(getUserConnectResponseData)
.catch(error => {
// Handle/report error
});
There are a couple of other things to point out, though:
getUserConnectRequestData is falling prey to a promise anti-pattern: You don't need new Promise when you already have a promise (from fetch) to use.
You need to check for HTTP success before calling .json() on the response. Sadly, fetch only rejects on network errors, not HTTP errors.
Here's an updated version of getUserConnectRequestData:
export const getUserConnectResponseData = (responseData) => {
return fetch('https://hghghgghghg3223223', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
Req_Type: responseData.Req_Type,
Language_Code: responseData.Language_Code,
User_ID: responseData.User_ID,
Session_ID: responseData.Session_ID,
Session_Key: responseData.Session_Key,
Client_Type: responseData.Client_Type,
Req_Data: {
Bridge_ID: responseData.Bridge_ID,
},
}),
})
.then((response) => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
});
};
Because of that need for the check, I never use fetch directly, I have wrappers to do the check so I don't have to code it Every Single Time.
// General purpose
function fetchGeneral(...args) {
return fetch(...args)
.then((response) => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response;
});
}
// JSON
function fetchJSON(...args) {
return fetch(...args)
.then((response) => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
});
}
Those reject on both network and HTTP errors.
I am working on Reactjs redux on front-end and Rails API as a back-end.
So now I call API with Fetch API method but the problem is I cannot get readable error message like what I got inside the network tabs
this is my function
export function create_user(user,userInfoParams={}) {
return function (dispatch) {
dispatch(update_user(user));
return fetch(deafaultUrl + '/v1/users/',
{
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(userInfoParams)
})
.then(function(response) {
console.log(response);
console.log(response.body);
console.log(response.message);
console.log(response.errors);
console.log(response.json());
dispatch(update_errors(response));
if (response.status >= 400) {
throw new Error("Bad response from server");
}
})
.then(function(json){
console.log("succeed json re");
// We can dispatch many times!
// Here, we update the app state with the results of the API call.
dispatch(update_user(json));
});
}
}
But when errors came I cannot figure out how to get readable response message like I got when I check on my browser network tabs
So this is what I got from the network tabs when I got errors.
My console
This is my rails code
def create
user = User.new(user_params)
if user.save
#UserMailer.account_activation(user).deliver_now
render json: user, status: 201
else
render json: { errors: user.errors }, status: 422
end
end
But I cannot find out how can I get that inside my function
Since the text is hidden inside promise within response object, it needs to be handled like a promise to see it.
fetch(bla)
.then(res => {
if(!res.ok) {
return res.text().then(text => { throw new Error(text) })
}
else {
return res.json();
}
})
.catch(err => {
console.log('caught it!',err);
});
Similar to your answer, but with a bit more explanation... I first check if the response is ok, and then generate the error from the response.text() only for the cases that we have a successful response. Thus, network errors (which are not ok) would still generate their own error without being converted to text. Then those errors are caught in the downstream catch.
Here is my solution - I pulled the core fetch function into a wrapper function:
const fetchJSON = (...args) => {
return fetch(...args)
.then(res => {
if(res.ok) {
return res.json()
}
return res.text().then(text => {throw new Error(text)})
})
}
Then when I use it, I define how to handle my response and errors as needed at that time:
fetchJSON(url, options)
.then((json) => {
// do things with the response, like setting state:
this.setState({ something: json })
})
.catch(error => {
// do things with the error, like logging them:
console.error(error)
})
even though this is a bit old question I'm going to chime in.
In the comments above there was this answer:
const fetchJSON = (...args) => {
return fetch(...args)
.then(res => {
if(res.ok) {
return res.json()
}
return res.text().then(text => {throw new Error(text)})
})
}
Sure, you can use it, but there is one important thing to bare in mind. If you return json from the rest api looking as {error: 'Something went wrong'}, the code return res.text().then(text => {throw new Error(text)}) displayed above will certainly work, but the res.text() actually returns the string. Yeah, you guessed it! Not only will the string contain the value but also the key merged together! This leaves you with nothing but to separate it somehow. Yuck!
Therefore, I propose a different solution.
fetch(`backend.com/login`, {
method: 'POST',
body: JSON.stringify({ email, password })
})
.then(response => {
if (response.ok) return response.json();
return response.json().then(response => {throw new Error(response.error)})
})
.then(response => { ...someAdditional code })
.catch(error => reject(error.message))
So let's break the code, the first then in particular.
.then(response => {
if (response.ok) return response.json();
return response.json().then(response => {throw new Error(response.error)})
})
If the response is okay (i.e. the server returns 2xx response), it returns another promise response.json() which is processed subsequently in the next then block.
Otherwise, I will AGAIN invoke response.json() method, but will also provide it with its own then block of code. There I will throw a new error. In this case, the response in the brackets throw new Error(response.error) is a standard javascript object and therefore I'll take the error from it.
As you can see, there is also the catch block of code at the very end, where you process the newly thrown error. (error.message <-- the error is an object consisting of many fields such as name or message. I am not using name in this particular instance. You are bound to have this knowledge anyway)
Tadaaa! Hope it helps!
I've been looking around this problem and has come across this post so thought that my answer would benefit someone in the future.
Have a lovely day!
Marek
If you came to this question while trying to find the issue because response.json() throws "Unexpected token at position..." and you can't find the issue with the JSON, then you can try this, basically getting the text and then parsing it
fetch(URL)
.then(async (response) => {
if (!response.ok) {
const text = await response.text()
throw new Error(text)
}
// Here first we convert the body to text
const text = await response.text()
// You can add a console.log(text), to see the response
// Return the JSON
return JSON.parse(text)
})
.catch((error) => console.log('Error:', error))
.then((response) => console.log(response))
I think you need to do something like this
export function create_user(user,userInfoParams={}) {
return function (dispatch) {
dispatch(update_user(user));
return fetch(deafaultUrl + '/v1/users/',
{
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(userInfoParams)
})
.then(function(response) {
console.log(response);
console.log(response.body);
console.log(response.message);
console.log(response.errors);
console.log(response.json());
return response.json();
})
.then(function(object){
if (object.errors) {
dispatch(update_errors(response));
throw new Error(object.errors);
} else {
console.log("succeed json re");
dispatch(update_user(json));
}
})
.catch(function(error){
this.setState({ error })
})
}
}
You can access the error message with this way:
return fetch(deafaultUrl + '/v1/users/',
{
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(userInfoParams)
})
.then(function(response) {
console.log(response);
console.log(response.body);
console.log(response.message);
console.log(response.errors);
console.log(response.json());
dispatch(update_errors(response));
if (response.status >= 400) {
throw new Error("Bad response from server");
}
})
.then(function(json){
console.log("succeed json re");
// We can dispatch many times!
// Here, we update the app state with the results of the API call.
dispatch(update_user(json));
})
// here's the way to access the error message
.catch(function(error) {
console.log(error.response.data.message)
})
;
The best choice is not to catch the error in the fetch because this will be useless:
Just in your api put a response with not code error
static GetInvoicesAllData = async (req,res) =>
{
try{
let pool = await new Connection().GetConnection()
let invoiceRepository = new InvoiceRepository(pool);
let result = await invoiceRepository.GetInvoicesAllData();
res.json(result.recordset);
}catch(error){
res.send(error);
}
}
Then you just catch the error like this to show the message in front end.
fetch(process.env.REACT_APP_NodeAPI+'/Invoices/AllData')
.then(respuesta=>respuesta.json())
.then((datosRespuesta)=>{
if(datosRespuesta.originalError== undefined)
{
this.setState({datosCargados:true, facturas:datosRespuesta})
}
else{ alert("Error: " + datosRespuesta.originalError.info.message ) }
})
With this you will get what you want.
You variables coming back are not in response.body or response.message.
You need to check for the errors attribute on the response object.
if(response.errors) {
console.error(response.errors)
}
Check here https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
You should actually be returning an error response code from the server and use the .catch() function of the fetch API
First you need to call json method on your response.
An example:
fetch(`${API_URL}`, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(userInfoParams)
})
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => {
console.log("error", err)
});
Let me know the console log if it didn't work for you.