I'm trying to load bunch of text files using multiple fetch requests via Promise.all. Here's how it looks:
////////////////////////////////////////
const loadTextFiles = (allUrls, textUrls) => {
const requests = textUrls.map(url => fetch(url));
return Promise.all(requests)
.then(responses => Promise.all(
responses.map(response => response.text())
))
.then(texts => texts.forEach(
(text, i) => allUrls[textUrls[i]] = text
))
.catch(err => {
// log("HERE");
throw exception("networkLoader",
"loadTextFiles",
`error in loadTextFiles: ${err.toString()}`);
});
};
allUrls and textUrls are two arrays that contain the urls of all resources (files with any extension) and text resources (files with .txt extension), respectively. In fact, textUrls is constructed from allUrls (never mind why or how). The results are stored inside the allUrls array. For example, if allUrls = [ "image.png", "essay.txt" ] , then textUrls = [ "essay.txt" ] . Now, if I call loadTextFiles:
await loadTextFiles(allUrls, textUrls);
I will get access to the contents of "essay.txt" by accessing allUrls["essay.txt"]. Things are okay so long as all text files exist and can be retrieved.
The problem is, despite the fact that I have written a catch to detect problems in Promise.all, it doesn't work. For instance, if I request fileThatDoesNotExist.txt I get a 404 (Not Found) in the browser console but my code inside catch doesn't run. I tested it even with a custom log function which also doesn't run. I'd like to catch the error and re-throw it. How do I do that?
Edit: When I said "I'd like to catch the error" I meant that I want to get notified of 404 error and throw an exception (in this case a custom exception object)
This question can be solved in the following way:
const loadTextFiles = (allUrls, textUrls) => {
const requests = textUrls.map(url => fetch(url));
return Promise.all(requests)
.then(responses => Promise.all(
responses.map(response => {
if (response.ok) {
return response.text();
}
throw exception("networkLoader",
"loadTextFiles",
`couldn't load ${response.url}`);
})
))
.then(texts => texts.forEach(
(text, i) => allUrls[textUrls[i]] = text
))
.catch(err => {
if (exception.isException(err)) {
throw err;
}
throw exception("networkLoader",
"loadTextFiles",
`error: ${err.toString()}`);
});
};
We test each response returned by fetch by checking its ok property, and if this property is false, then an exception is thrown.
Related
Can i do this:
const [borderCountries, setBorderCountries] = useState([])
useEffect(() => {
country.borders.forEach(c => {
fetch(`https://restcountries.eu/rest/v2/alpha/${c}`)
.then(res => res.json())
.then(data => setBorderCountries([...borderCountries,data.name]))
})
}, [])
Country borders is a prop passed to the component. If not what can I do?
You can, but not quite like that, for a couple of reasons:
Each fetch operation will overwrite the results of the previous one, because you're using borderCountries directly rather than using the callback version of setBorderCountries.
Since the operation depends on the value of a prop, you need to list that prop in the useEffect dependencies array.
The minimal change is to use the callback version:
.then(data => setBorderCountries(borderCountries => [...borderCountries,data.name]))
// ^^^^^^^^^^^^^^^^^^^
...and add country.borders to the useEffect dependency array.
That will update your component's state each time a fetch completes.
Alternatively, gather up all of the changes and apply them at once:
Promise.all(
country.borders.map(c =>
fetch(`https://restcountries.eu/rest/v2/alpha/${c}`)
.then(res => res.json())
.then(data => data.name)
})
).then(names => {
setBorderCountries(borderCountries => [...borderCountries, ...names]);
});
Either way, a couple of notes:
Your code is falling prey to a footgun in the fetch API: It only rejects its promise on network failure, not HTTP errors. Check the ok flag on the response object before calling .json() on it to see whether there was an HTTP error. More about that in my blog post here.
You should handle the possibility that the fetch fails (whether a network error or HTTP error). Nothing in your code is currently handling promise rejection. At a minimum, add a .catch that reports the error.
Since country.borders is a property, you may want to cancel any previous fetch operations that are still in progress, at least if the border it's fetching isn't still in the list.
Putting #1 and #2 together but leaving #3 as an exercise for the reader (not least because how/whether you handle that varies markedly depending on your use case, though for the cancellation part you'd use AbortController), if you want to update each time you get a result
const [borderCountries, setBorderCountries] = useState([]);
useEffect(() => {
country.borders.forEach(c => {
fetch(`https://restcountries.eu/rest/v2/alpha/${c}`)
.then(res => {
if (!res.ok) {
throw new Error(`HTTP error ${res.status}`);
}
return res.json();
})
.then(data => setBorderCountries(borderCountries => [...borderCountries, data.name]))
// ^^^^^^^^^^^^^^^^^^^
.catch(error => {
// ...handle and/or report the error...
});
});
}, [country.borders]);
// ^^^^^^^^^^^^^^^
Or for one update:
const [borderCountries, setBorderCountries] = useState([]);
useEffect(() => {
Promise.all(
country.borders.map(c =>
fetch(`https://restcountries.eu/rest/v2/alpha/${c}`)
.then(res => res.json())
.then(data => data.name)
})
)
.then(names => {
setBorderCountries(borderCountries => [...borderCountries, ...names]);
})
.catch(error => {
// ...handle and/or report the error...
});
}, [country.borders]);
// ^^^^^^^^^^^^^^^
You can but it's Not a good practice.
Currently, I was taking a course:Front-End Web Development with React in coursera, and I was stuck at a point where the instructor was showing how to fetch data using cross-fetch. Here he was showing
export const fetchDishes = () => (dispatch) => {
return fetch(baseUrl + 'dishes')
.then(
(response) => {
if (response.ok) {
return response;
} else {
var error = new Error(
'Error ' + response.status + ': ' + response.statusText
);
error.response = response;
throw error;
}
},
//manually handle error if the server didn't response
(error) => {
var errmess = new Error(error.message);
throw errmess;
}
)
.then((response) => response.json())
.then((dishes) => dispatch(addDishes(dishes)));
.catch(error => dispatch(dishesFailed(error.message)));
};
But my ESLint showing me error and suggest to use try...catch block.
image
But I was wondering why this error occurs even though the instructor write it as above and run the application perfectly? I have no idea how to convert this code into a try...catch block.
#erik_m give a solution but I did get that is semicolon mean terminate the promise chain? And one more thing which temp me that the instructor didn't import fetch (like import fetch from 'cross-fetch')then how my application is using fetch? He just showed to do yarn add cross-fetch#2.1.0 Did fetch is inherited by default with react application?
You are terminating the call one line above the catch.
.then((response) => response.json())
.then((dishes) => dispatch(addDishes(dishes)));
.catch(error => dispatch(dishesFailed(error.message)));
on the line right above the .catch, remove the semicolon, and the paren. Your catch is not properly part of your then statement.
Edit: Can you try to replace the last three lines with this? You have your parens and semi colons in the wrong place. So the program thinks you are ending your promise after the second then, so it is not recognizing the "catch" portion.
.then((response) => response.json())
.then((dishes) => dispatch(addDishes(dishes))
.catch(error => dispatch(dishesFailed(error.message))));
I'm using Promises to automatically fill up my dropdowns on page load (I have multiple dropdowns on the page).
Here is the code I use to return the following:
$(document).ready(function(){
var urls = ['getBrands', 'getTags'];
Promise.all(urls.map(u=>fetch(u))).then(
responses => Promise.all(responses.map(res => res.json()))
).then(
texts=>console.log(texts)
).then(
result => console.log(result[0]) //This is where the error is
)
});
This prints the response to the console correctly, but throws an error when I try to read the individual result. The error is Uncaught(in promise) TypeError: cannot read property '0' of undefined
The problem is your first fulfillment handler returns undefined, which becomes the fulfillment value of the promise it returns.
If you just remove it, your second fulfillment handler will see the values.
$(document).ready(function(){
var urls = ['getBrands', 'getTags'];
Promise.all(urls.map(u=>fetch(u))).then(
responses => Promise.all(responses.map(res => res.json()))
).then(
result => console.log(result[0])
)
});
Alternatively, have it return what it receives:
$(document).ready(function(){
var urls = ['getBrands', 'getTags'];
Promise.all(urls.map(u=>fetch(u))).then(
responses => Promise.all(responses.map(res => res.json()))
).then(texts => {
console.log(texts);
return texts;
}).then(
result => console.log(result[0])
)
});
Side note: That code breaks one of the Rules of Promises, which is:
Handle rejection, or pass the promise chain to something that will.
You probably want to add a rejection handler via .catch.
Side note 2: Assuming fetch is the standard fetch, your code is missing a check for success. This is a footgun in the fetch API (I write about it here). Fixing it:
Promise.all(
urls.map(u=>fetch(u).then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return res.json();
}))
).then(texts => {
Note that that eliminates the need for the second Promise.all as well, but handling each fetch individually earlier.
So, I am fetching a url and my API returns either the data or a 500 error with a few error codes. I am trying to capture the error code and display it in React. In the console, I see the error, but it looks like this:
So, I see the 'Not Found' which is the text I want to display, but how do I get this text out of the error format so I can use it elsewhere?
Here is my code, hopefully this make sense what I am trying to do:
callApi = async (url) => {
const response = await fetch(url);
const body = await response.json();
if (response.status !== 200) throw Error(body.messages);
return body;
};
this.callApi(url)
.then(results => {
this.function(results);
})
.catch(err => {
console.log(err);
if (err === "Not Found") {
console.log('Not Found')
}
if (err === "No Access") {
console.log('No Access')
}
});
JavaScript errors inherit from the base Error object. This means they will almost always have a set message property, meaning you can simply do:
console.log(err.message);
Also to be clear, fetch is a browser API, and has nothing to do with React.
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
});