I recently have learned something about fetch() and promise, and now I need to use it in project. Here I have a fetch() function, which works very well, but I think, it must catch an error. So, what is the best way to catch error in fetch() functions? And i need to catch them in both then()?
Here some code:
const endpoint = 'http://localhost:3030/api/hotels';
const promise = fetch(endpoint)
.then(res => res.json(), err => {
console.log(err);
})
.then(parseRooms, err => {
console.log(err);
})
Thank you !
Use the fact that promise handlers chain together. Each call to then or catch creates a new promise, which is chained to the previous one.
So in your case:
const promise = fetch(endpoint)
.then(res => res.json())
.then(parseRooms)
.catch(error => {
// Do something useful with the error
});
I'm assuming there that parseRooms throws an error if there's a problem with the structure it receives.
You probably want to check res.ok in there, too, since fetch only fails if there was a network error, not if there was an HTTP error such as a 404:
const promise = fetch(endpoint)
.then(res => {
if (!res.ok) {
throw new Error(); // Will take you to the `catch` below
}
return res.json();
})
.then(parseRooms)
.catch(error => {
// Do something useful with the error
});
Related
I'm having some trouble understanding what I'm doing wrong. I have a function that receives a url to which should make a GET request, in case of success should fill a combo with the received data (this depends which function calls it), in case of fail it should execute some common code.
getFirstCombo = () => {
this.getFromApi('/First/GetAll')
.then(data => this.setState({firstComboOptions: this.parseCombo(data)}))
.catch(error => console.log('ERROR2: ', error));
}
getSecondCombo = () => {
this.getFromApi('/Second/GetAll')
.then(data => this.setState({secondComboOptions: this.parseCombo(data)}))
.catch(error => console.log('ERROR2: ', error));
}
parseCombo = (data: any) => {
const combo = data.map(item => (
{ label: item.description, value: item.id }
));
return combo;
}
getFromApi = (url: string) : Promise<any> => {
return restApiAxios.get(url)
.then(response => {
return response.data;
})
.catch(error => {
console.log('ERROR: ', error);
});
}
this code is executed on the componentDidMount of the react component, but when it fails, it first prints :
ERROR: Error: Network Error
at createError (createError.js:16)
at XMLHttpRequest.handleError (xhr.js:83)
and immediately after:
PanelDatos.tsx:50 ERROR2: TypeError: Cannot read property 'map' of undefined
at PanelDatos.parseCombo (PanelDatos.tsx:55)
at PanelDatos.tsx:50
so, when failing executes the catch block from getFromApi and then it tries to execute the then block in getFirstCombo, which triggers the catch block from the same function cause data does not exist, why is that? shouldnt it just execute the first catch?
thanks in advance
.catch returns a promise much like .then, allowing you to return a custom value and handle it that way.
Try doing the following to observe the effect:
Promise
.reject(1)
.catch(e => e) // Catch the error and return it
.then(console.log) // will log 1 to the console
This means you'll need to add some checks if you want to continue to use promises like this:
Promise
.reject(new Error('haha'))
.catch(err => ({err}))
.then(({err, data}) => {
if(err) return // Do nothing
// enter code here
})
However, using async / await will improve readability even more:
getFirstCombo = async () => {
let response
try {
response = await this.getFromApi('/First/GetAll')
} catch (e) {
return // Exit early
}
let parsed
try {
parsed = this.parseCombo(data)
} catch (e) {
console.log(e)
return // Exit early
}
return this.setState({firstComboOptions: parsed})
}
And, of course, throw the error again in your catch block in your api to allow it to handle api calls.
This is happening since inside getFromApi catch method on the error you are not returning anything, so by default, it is returning a resolved promise with null response and the execution goes inside getFirstCombo then method, causing another error. You can update your code to resolve this like:
getFromApi = (url: string): Promise<any> => {
return restApiAxios.get(url)
.then(response => response.data)
.catch(error => Promise.reject(error));
}
The static Promise.reject function returns a Promise that is rejected. So, it will go directly into catch of wherever getFromApi is called.
DEMO:
async function getFromApi(url) {
return fetch(url) // rejects
.then(response => response.json())
.catch(err => Promise.reject(err))
}
async function getFirstCombo() {
getFromApi('https://no-such-server.abcd')
.then(data => console.log('data: ', data))
.catch(error => console.log('ERROR2: ', error));
}
getFirstCombo()
DEMO #2 (With getFirstCombo function not having any catch block) :
async function getFromApi(url) {
return fetch(url) // rejects
.then(response => response.json())
.catch(err => {
console.log('ERROR in getFromApi(): ', err);
return null; // return null, empty array, 0 or false... as per your requirement
})
}
async function getFirstCombo() {
getFromApi('https://no-such-server.abcd')
.then(data => console.log('data: ', data))
// Same value set in catch block of getFromApi will return in this then() block
// Validate this `data` variable before processing it further like:
// if(data === null) this means an error had occurred
// else continue with your logic
}
getFirstCombo()
This question already has answers here:
What is the explicit promise construction antipattern and how do I avoid it?
(3 answers)
Closed 3 years ago.
Axios is described as Promise-based, so is there a need for returning a new Promise when using Axios to query for data?
app.get('/api/nearbyRecommendations', async (req, res) => {
if(!req.query) return res.send({ error: 'Please enable location to get recommendations.' })
try {
const { longitude, latitude } = req.query
const locationName = await location.getLocationName(longitude, latitude)
res.send(locationName)
} catch (error) {
res.send(error)
}
})
I am making a GET request to the MapBox API, but I do not seem to ever get any errors despite setting up the catch block for my Axios request, even if I throw a new Error in the .then() block.
const getLocationName = async (latitude, longitude) => {
return new Promise((resolve, reject) => {
axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${longitude},${latitude}.json?access_token=${darkSkyAPIKey}`, {json: true})
.then(response => {
if(!response.data) return reject({ error: 'No location found.' })
resolve(response.data)
}).catch(error => {
reject(error)
})
})
}
If possible, do help and point out anything that may be altered to follow best practices.
You can just return the promise immeditately without using an async function:
const getLocationName = (latitude, longitude) => {
return axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${longitude},${latitude}.json?access_token=${darkSkyAPIKey}`, {json: true})
.then(response => {
if(!response.data)
throw Error('No location found.')
return response.data;
}).catch(error => {
console.log(error);
throw error;
})
}
Axios.get already returns a promise to you. If you also define the function as async that means that the returned promise will be wrapped in a promise again. So in your example you were triple wrapping the response in a promise. If you replace it with the getLocationName function with a regular function, usage in the first code snippet will remain exactly the same.
I've currently got an ES6 class with a constructor and two methods. I'm a tad confused as to why using Promise.reject(ex) within the .then() is resolving undefined. If someone wouldn't mind explaining what I'm doing wrong that would be much appreciated.
I have a method called getYaml() which contains the following:
_getYaml(recordId) {
return new Promise((resolve, reject) => {
fs.readFile(this.workingDir + '/' + recordId + '.yaml', 'utf8', function(err, data) {
if (err) reject(err)
resolve(data)
})
})
}
I then have another method called getCompDoc which makes use of the other method like so:
getCompDoc(recordId) {
return this._getYaml(recordId).then(data => {
let yaml = data
let yamlObj
try {
yamlObj = YAML.safeLoad(yaml)
} catch (ex) {
ex.message = `Failure to parse yaml. Error: ${ex.message}`
logger.error(ex.message, {}, ex)
return Promise.reject(ex)
}
let compDoc = {
// ...
}
return compDoc
}).catch(err => {
logger.error(err, {}, err)
})
}
I then have a test to check that the YAML parsing error is caught and then a promise rejected which looks like so:
describe('error cases', () => {
const fakeRecordId = 'SomeYaml'
beforeEach(() => {
sinon.stub(myClass, '_getYaml').returns(Promise.resolve('{{&^%}egrinv&alidgj%^%^&$£#£#£}'))
})
afterEach(() => {
myClass._getYaml.restore()
})
it('Error parsing yaml, rejects with error', () => {
return expect(myClass.getCompDoc(fakeRecordId)).to.be.rejected.then(response => {
expect(response.message).to.match(/Failure to parse yaml. Error: /)
})
})
})
Test output:
AssertionError: expected promise to be rejected but it was fulfilled with undefined
If I simply return the exception that is thrown within the getCompDoc method, I recieve the error as expected, however as soon as I use Promise.reject it resolves with undefined.
I was thinking of wrapping the getCompDoc in a return new Promise() however I wasn't sure if this would be an example of the Promise constructor anti-pattern. I would ideally like to reject this, instead of returning the error directly as then I can assert that the method was rejected and not fulfilled.
You 'swallow' the error in getCompDoc in your catch clause. Specifically, here's a simplified snippet representing your code:
let getYamlPromise = Promise.reject('REJECTED!');
let getCompDocPromise = getYamlPromise
.then(data => console.log('getYamlPromise', data))
.catch(error => console.error('getYamlPromise', error));
getCompDocPromise
.then(a => console.log('getCompDocPromise RESOLVED', a))
.catch(a => console.log('getCompDocPromise REJECTED', a));
As you can see, getCompDocPromise is resolved with undefined. If you would like to propagate the error, your catch clause will have to throw a new error or return a rejected promise:
let getYamlPromise = Promise.reject('REJECTED!');
let getCompDocPromise = getYamlPromise
.then(data => console.log('getYamlPromise', data))
.catch(error => {
console.error('getYamlPromise', error);
return Promise.reject(error);
});
getCompDocPromise
.then(a => console.log('getCompDocPromise RESOLVED', a))
.catch(a => console.log('getCompDocPromise REJECTED', a));
I have an authService which has the following fetch
export const handleForm = () => {
return fetch('http://localhost:5000/login')
.then(response => {
if (!response.ok) {
throw new Error("Email or password incorrect");
} else {
return response.json();
}
})
.then(json => json)
.catch(error => error);
};
I'm calling the method from my controller:
form.onsubmit = function (e) {
handleForm(e)
.then(response => {
console.log(response);
//setAuthenticatedUser(response);
// setDomUser(response.user);
})
.catch(error => {
console.log(error);
document.querySelector('#error').innerHTML = error;
})
};
I've tried a few things to get the error in my catch, in the controller
But I keep getting the response callback fired.
The error from my authService isn't being caught
I've Tried
throw new Error();
Promise.reject();
See Promise.prototype.catch():
Syntax Section
p.catch(onRejected);
[...]
The Promise returned by catch() is rejected if onRejected throws an error or returns a Promise which is itself rejected; otherwise, it is resolved.
Using .catch(error => error) as a callback returns the exception. So it neither "throws an error" nor "returns a Promise which is itself rejected". The promise returned by it is thus resolved, not rejected.
Simply remove .catch(error => error) and it will work. And when you're at it, also remove .then(json => json) as it is useless.
Hi everyone running into a problem with a post service I created in angular. Struggling to catch the error from my component when I call the service. I was able to catch the error from the service but I need to do this from my component to properly handle the error. Any advice would be greatly appreciated.
Service
sendData(obj) {
let promise = new Promise((resolve) => {
this.http.post(this.URL, obj, this.httpOptions)
.toPromise()
.then(
res => {
console.log(res);
resolve(res);
}
)
//.catch((err) => {
// console.log(err);
// throw err;
//});
});
return promise;
}
Component
this._myservice.sendData(this.myobj)
.then(function (res) {
console.log('data sent');
})
.catch(error => {
console.warn('from component:', error);
// this console warn never gets logged out
});
Do I need to update something in my service to allow me to catch the error from the component?
You're creating your own Promise here, but you never call reject if the Promise you're wrapping rejects (throws an error). This is known as the the new Promise antipattern. In your case, you can simply remove this wrapper and replace the call to resolve with a return in order to achieve what you need, like so:
sendData(obj) {
return this.http.post(this.URL, obj, this.httpOptions)
.toPromise()
.then(res => {
console.log(res);
return res;
});
}
In order to provide more context, you could fix your original problem by calling reject. This would look like this:
// DONT'T DO THIS
sendData(obj) {
let promise = new Promise((resolve, reject) => {
this.http.post(this.URL, obj, this.httpOptions)
.toPromise()
.then(res => {
console.log(res);
resolve(res);
})
.catch((err) => {
console.log(err);
reject(err); // Here.
});
});
return promise;
}
But, as I said above, this is overcomplicated and unnecessary. I hope that it demonstrates how the Promise your component has access to could never see errors that occurred in the HTTP call.