This question already has answers here:
Suppress Chrome 'Failed to load resource' messages in console
(3 answers)
Correct Try...Catch Syntax Using Async/Await
(6 answers)
Closed 3 years ago.
I'm a little bit confused about the right way to handle errors in an async function in javascript.
async function getWeatherAW(woeid) {
try {
const result = await fetch(`https://cors-anywhere.herokuapp.com/https://www.metaweather.com/api/location/${woeid}/`);
const data = await result.json();
const tomorrow = data.consolidated_weather[1];
return data;
} catch(error) {
console.log(`Error catch-Statement: ${error}`);
}
}
const wrongData = 3442;
getWeatherAW(wrongData)
.then(data => {
console.log(data);
})
.catch(error => {
console.log(`Error .catch()-Statement: ${error}`);
})
This code ended up giving this result. I'm curious about why even if the catch-statement is triggered a 404 error is printed out to the console..
Would it also be better to not return the result from the async function and instead proceed in the function? I mean the data in the then-block seems to be undefinded when an error occurs in first place.. and if I need to call the function more often, I don't always need to write the then and catch, right?
Thanks in advance!
The console prints a 404 message for all failed requests, regardless of whether the error is handled.
It's a debugging aid to show you that a request failed, not an indication that the failure wasn't handled.
the error says cannot read property '1' of undefined
async function getWeatherAW(woeid) {
try {
const result = await fetch(`https://cors-anywhere.herokuapp.com/https://www.metaweather.com/api/location/${woeid}/`);
const data = await result.json();
const tomorrow = data.consolidated_weather[1]; <----- this is undefined
return data;
} catch(error) {
console.log(`Error catch-Statement: ${error}`);
}
}
const wrongData = 3442;
getWeatherAW(wrongData)
.then(data => {
console.log(data);
})
.catch(error => {
console.log(`Error .catch()-Statement: ${error}`);
})
My guess is tomorrow is initializing before data has been initialized - or that data does not have key consolidate_weather
doing fetch.then() may get the result you want
let tomorrow;
return fetch(url).then((result) => {
const data = JSON.parse(result);
tomorrow = data.consolidated_weather[1];
return data;
}, (error) => {
console.log(`Error .catch()-Statement: ${error}`);
})
Related
I know this can be solved by writing all codes to async-await style, then can simply write let text = await res.text(); then try catch the JSON.parse(text) and then do decision.
But here I just want to know if there is any way we can achieve that in .then/.catch style.
Consider the below code:
async function test() {
try {
let n = await fetch("https://stackoverflow.com")
.then(res => {
return res.json()
})
.then(data => data.results.length)
.catch(e => {
console.error("Catch 2", e)
})
}
catch (e) {
console.error("Catch 3", e)
}
}
if we execute this function in the browser devtools(F12) with await test(), then there will be an error catch by the "Catch 2" clause. But in the error detail we can only see some logs like JSON parse error.
We cannot see the full text of the response body.
Is there any way that can get the text when the JSON parsing failed?
Your best bet is to look at the response in your devtools' network tab. That will show you the full response.
But if you want to do it in code, you can separate reading the response from parsing it by using the text method instead of the json method, then parsing the text yourself.
The parsing error may be down to the fact you aren't checking for HTTP success. As I noted on my old anemic blog here, fetch only rejects its promise on network errors, not HTTP errors (like 404, 500, etc.). To check for HTTP success, look at the ok or status properties.
Here's the minimal-changes version separating reading the response from parsing it, and checking for HTTP success before reading it at all:
async function test() {
try {
let n = await fetch("https://stackoverflow.com")
.then((res) => {
if (!res.ok) { // ***
throw new Error(`HTTP error ${res.status}`); // ***
} // ***
return res.text(); // ***
})
.then((text) => {
// *** you can look at `text` here in a debugger, or
// *** log it, save it, etc., before parsing below
// *** (which might throw an error)
try {
const data = JSON.parse(text); // ***
return data.results.length;
} catch (error) {
console.error("Parsing error", e);
console.error("Text we were parsing:", text);
}
})
.catch((e) => {
console.error("Catch 2", e);
});
// ...do something with `n`...
} catch (e) {
console.error("Catch 3", e);
}
}
But a couple of things there:
I wouldn't mix async/await with explicit promise callbacks like that.
With that and with your original code, errors will result in n receive the value undefined, because the catch handlers (and my new try/catch block in the then handler) don't return anything.
Instead:
async function test() {
try {
const res = await fetch("https://stackoverflow.com");
if (!res.ok) {
throw new Error(`HTTP error ${res.status}`);
}
const text = await res.text();
// *** you can look at `text` here in a debugger, or
// *** log it, save it, etc., before parsing below
// *** (which might throw an error)
try {
const data = JSON.parse(text);
const n = data.results.length;
// ...do something with `n`...
} catch (error) {
console.error("Parsing error", e);
console.error("Text we were parsing:", text);
}
} catch (e) {
console.error("Catch 3", e);
}
}
Or if you want to respond differently to the parsing error, wrap that bit in a try/catch, etc.
You shouldn't confuse the catch which catching errors in the fetch function itself - with the response errors
fetch("/developer.mozilla.org")
.then(res => {
if (!res.ok) {
console.log("there was an error also here") <= this code also runs
console.log("response is", res);
}
return res.json()
})
.then(data => data.results.length)
.catch(e => {
console.error("Catch 2", e);
})
In your case, you tried converting data -> JSON w/o success, it failed and dropped to the "catch" section.
but to inspect the response - you can dump it in the first section above where I added res.ok
I believe you could do something like this when using promise style Javascript:
const fetchDataPromise = () => {
fetch('https://stackoverflow.com').then((res) => {
res.json().then((jsonData) => {
console.log(jsonData)
}).catch((err) => {
console.error(err)
res.text().then((rawData) => {
console.log(rawData)
}).catch((err) => console.error(err))
})
})
}
Also more intuitive approach would be to use async/await (the trade-off is that you will have to do the API call again):
const fetchData = async () => {
try {
const res = await fetch('https://stackoverflow.com')
const jsonData = await res.json()
console.log(jsonData)
} catch (err) {
try {
console.error(err)
const res = await fetch('https://stackoverflow.com')
const rawData = await res.text()
console.log(rawData)
} catch (rawError) {
console.error(rawError)
}
}
}
I am trying to test my react project locally with my computer and with my phone. I am using JavaScript not TypeScript.
When I run the project on my computer everything works fine, but when I try to load it on my phone, I get an error: Unhandled Rejection (TypeError): undefined is not an object (evaluating 'scheduleArr.forEach'). I thought I was using async and await correctly because this code workes on my computer. I'm confused as to why this code works on one platform but not the other.
async function getSchedule() {
let scheduleArr = await axios.get('api/schedule/')
.then(response => {
return response.data;
})
.catch((error) => {
console.log(`ERROR: ${error}`);
});
scheduleArr.forEach(game => {
/* do stuff */
}
});
I think this problem is directly related to async and await because when I comment out this function, my project loads correctly on my phone.
Can anyone help me understand what I'm doing wrong?
You can't use the async/await pattern with then. Either use it like :
async function getSchedule() {
try {
let scheduleArr = await axios.get("api/schedule/");
console.log(scheduleArr.data);
} catch (err) {
console.log(`ERROR: ${err}`);
}
scheduleArr.forEach(game => {
/* do stuff */
});
}
Or with the default pattern :
function getSchedule() {
axios
.get("api/schedule/")
.then(response => {
let scheduleArr = response.data;
// Do this inside the 'then'
scheduleArr.forEach(game => {
/* do stuff */
});
})
.catch(error => {
console.log(`ERROR: ${error}`);
});
}
Note that I moved your foreach loop into the then. It is asynchronous and therefore need to be triggered only when you get the result of your api call.
I figured out what the issue was. It had nothing to do with async await or axios.
This was my code before
function getSchedule() {
axios
.get("http://localhost:5000/api/schedule/")
.then(response => {
let scheduleArr = response.data;
// Do this inside the 'then'
scheduleArr.forEach(game => {
/* do stuff */
});
})
.catch(error => {
console.log(`ERROR: ${error}`);
});
}
I changed my API call to use my actual local IP instead of localhost
function getSchedule() {
axios
.get("http://192.168.X.XX:5000/api/schedule/")
.then(response => {
let scheduleArr = response.data;
// Do this inside the 'then'
scheduleArr.forEach(game => {
/* do stuff */
});
})
.catch(error => {
console.log(`ERROR: ${error}`);
});
}
Nesting my code in the .then block did fix my undefined error even with the bad url. Thank you for that suggestion #Quentin Grisel.
Fixing my url let me test it on different devices.
I have a function which fetch a data from Firestore :
getLastTime(collectionName: string) {
const docRef = this.afs.firestore.collection(collectionName).doc(this.User).collection('lastTime').doc('lastTime');
docRef.get().then(doc => {
if (doc.exists) {
this.get = doc.data().lastTime;
} else {
this.get = 'Never done';
}
}).catch(error => {
console.log('Error getting document:', error);
});
return this.get;
}
For my test I actually have a string value inside the doc 'lastTime' which is a string.
Inside ngOnInit(), I called my function and console.log the result
this.InjuredLastTime = this.getLastTime('INJURY');
console.log(this. this.InjuredLastTime);
Normally I should have my string printed inside the console but I got undefined...
Maybe it's because Firestore do not fetch fast enough my data, but I am quiet surprised since Firestore is quiet fast normally...
You don't actually wait for the promise that is created by docRef.get() before you return from getLastTime(). So, unless the call to firebase is instant (e.g. never) this won't work.
The correct solution really depends on what you are doing with this.InjuredLastTime. But one approach would just be to return a promise and set it after it is ready:
getLastTime(collectionName: string) {
const docRef = this.afs.firestore.collection(collectionName).doc(this.User).collection('lastTime').doc('lastTime');
return docRef.get().then(doc => {
if (doc.exists) {
return doc.data().lastTime;
} else {
return 'Never done';
}
}).catch(error => {
console.log('Error getting document:', error);
return null;
});
}
Then, instead of the assignment synchronously, do it asynchronously:
this.getLastTime('INJURY').then(result => { this.InjuredLastTime = result });
Data is loaded from Firestore asynchronously, since it may take some time before the data comes back from the server. To prevent having to block the browser, your code is instead allowed to continue to run, and then your callback is called when the data is available.
You can easily see this with a few well-placed log statements:
const docRef = this.afs.firestore.collection(collectionName).doc(this.User).collection('lastTime').doc('lastTime');
console.log('Before starting to get data');
docRef.get().then(doc => {
console.log('Got data');
});
console.log('After starting to get data');
If you run this code, you'll get:
Before starting to get data
After starting to get data
Got data
This is probably not the order that you expected the logging output in, but it is actually the correct behavior. And it completely explains why you're getting undefined out of your getLastTime function: by the time return this.get; runs, the data hasn't loaded yet.
The simplest solution in modern JavaScript is to mark your function as async and then await its result. That would look something like this:
async function getLastTime(collectionName: string) {
const docRef = this.afs.firestore.collection(collectionName).doc(this.User).collection('lastTime').doc('lastTime');
doc = await docRef.get();
if (doc.exists) {
this.get = doc.data().lastTime;
} else {
this.get = 'Never done';
}
return this.get;
}
And then call it with:
this.InjuredLastTime = await this.getLastTime('INJURY');
This question already has answers here:
Suppress Chrome 'Failed to load resource' messages in console
(3 answers)
Closed 3 years ago.
I am using React with axios and redux-promise. Axios does not appear to catch the 404 error, as below.
This is the code.
const url = FIVE_DAY_FORECAST_URL.replace("{0}", city);
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
return Promise.reject(error);
});
try{
const request = axios.get(`${url}`).then(e => {
debugger; }).catch(
e => {
debugger;
return "ERROR"; // THIS FIRES, BUT DOES NOT STOP THE CONSOLE ERROR
});
debugger;
return {
type: FETCH_FIVE_DAY_FORECAST,
payload: request
};
} catch {
debugger;
console.log("Error!"); // DOES NOT HELP AS THE 404 IS NOT A JAVASCRIPT ERROR, IT'S A VALID SERVER RESPONSE
}
}
I am using a number of techniques to tr to catch the console error:
.then() ==> the code runs through this block, but the error has already happened, and has been written to the console!
.catch() ==> the code runs through this block if the interceptors are not configured i.e. comment out axios.interceptors.response.use... .
try...catch ==> no effect (does not catch the network response as this is not really a javascript error!)
When using try...catch with axios you explicitly have to state the error response like so
catch(error) {
console.log('[error]', error.response);
// use the error.response object for some logic here if you'd like
}
Otherwise it just returns the string value.
With that response object you can then utilize some logic to do something based on the particular error. More info can be found here https://github.com/axios/axios/issues/960
I hope this helps.
You are trying to catch the Promise.reject and wrapping it in a try...catch. Only one of this will work.
You can either catch a promise rejection or wrap the promise in a try...catch and throw a new error on promise rejection and catch it in catch block.
Try this code
const url = FIVE_DAY_FORECAST_URL.replace("{0}", city);
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
return Promise.reject(error);
});
try {
const request = axios.get(`${url}`)
.then(
response => {
debugger;
})
.catch(
e => {
debugger;
throw e // throw the error and catch it
});
debugger;
return {
type: FETCH_FIVE_DAY_FORECAST,
payload: request
};
} catch {
debugger;
// Now you can catch the error in catch block
console.log("Error!");
}
// or you can't write async/await code
// make the the function is marked as `async`
try {
const response = await axios.get(`${url}`)
return {
type: FETCH_FIVE_DAY_FORECAST,
payload: response
};
} catch (e) {
console.error("Error happened during fetch");
console.error(e);
}
tl;dr - if you have to filter the promises (say for errored ones) don't use async functions
I'm trying to fetch a list of urls with async and parse them, the problem is that if there's an error with one of the urls when I'm fetching - let's say for some reason the api endpoint doesn't exists - the program crushes on the parsing with the obvious error:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: ext is not iterable
I've tried checking if the res.json() is undefined, but obviously that's not it as it complains about the entire 'ext' array of promises not being iterable.
async function fetchAll() {
let data
let ext
try {
data = await Promise.all(urls.map(url=>fetch(url)))
} catch (err) {
console.log(err)
}
try {
ext = await Promise.all(data.map(res => {
if (res.json()==! 'undefined') { return res.json()}
}))
} catch (err) {
console.log(err)
}
for (let item of ext) {
console.log(ext)
}
}
Question 1:
How do I fix the above so it won't crash on an invalid address?
Question 2:
My next step is to write the extracted data to the database.
Assuming the data size of 2-5mgb of content, is my approach of using Promise.all() memory efficient? Or will it be more memory efficient and otherwise to write a for loop which handles each fetch then on the same iteration writes to the database and only then handles the next fetch?
You have several problems with your code on a fundamental basis. We should address those in order and the first is that you're not passing in any URLS!
async function fetchAll(urls) {
let data
let ext
try {
data = await Promise.all(urls.map(url=>fetch(url)))
} catch (err) {
console.log(err)
}
try {
ext = await Promise.all(data.map(res => {
if (res.json()==! 'undefined') { return res.json()}
}))
} catch (err) {
console.log(err)
}
for (let item of ext) {
console.log(ext)
}
}
First you have several try catch blocks on DEPENDANT DATA. They should all be in a single try catch block:
async function fetchAll(urls) {
try {
let data = await Promise.all(urls.map(url=>fetch(url)))
let ext = await Promise.all(data.map(res => {
// also fixed the ==! 'undefined'
if (res.json() !== undefined) { return res.json()}
}))
for (let item of ext) {
console.log(ext)
}
} catch (err) {
console.log(err)
}
}
Next is the problem that res.json() returns a promise wrapped around an object if it exists
if (res.json() !== undefined) { return res.json()}
This is not how you should be using the .json() method. It will fail if there is no parsable json. You should be putting a .catch on it
async function fetchAll(urls) {
try {
let data = await Promise.all(urls.map(url => fetch(url).catch(err => err)))
let ext = await Promise.all(data.map(res => res.json ? res.json().catch(err => err) : res))
for (let item of ext) {
console.log(ext)
}
} catch (err) {
console.log(err)
}
}
Now when it cannot fetch a URL, or parse a JSON you'll get the error and it will cascade down without throwing. Now your try catch block will ONLY throw if there is a different error that happens.
Of course this means we're putting an error handler on each promise and cascading the error, but that's not exactly a bad thing as it allows ALL of the fetches to happen and for you to distinguish which fetches failed. Which is a lot better than just having a generic handler for all fetches and not knowing which one failed.
But now we have it in a form where we can see that there is some better optimizations that can be performed to the code
async function fetchAll(urls) {
try {
let ext = await Promise.all(
urls.map(url => fetch(url)
.then(r => r.json())
.catch(error => ({ error, url }))
)
)
for (let item of ext) {
console.log(ext)
}
} catch (err) {
console.log(err)
}
}
Now with a much smaller footprint, better error handling, and readable, maintainable code, we can decide what we eventually want to return. Now the function can live wherever, be reused, and all it takes is a single array of simple GET URLs.
Next step is to do something with them so we probably want to return the array, which will be wrapped in a promise, and realistically we want the error to bubble since we've handled each fetch error, so we should also remove the try catch. At that point making it async no longer helps, and actively harms. Eventually we get a small function that groups all URL resolutions, or errors with their respective URL that we can easily filter over, map over, and chain!
function fetchAll(urls) {
return Promise.all(
urls.map(url => fetch(url)
.then(r => r.json())
.then(data => ({ data, url }))
.catch(error => ({ error, url }))
)
)
}
Now we get back an array of similar objects, each with the url it fetched, and either data or an error field! This makes chaining and inspecting SUPER easy.
You are getting a TypeError: ext is not iterable - because ext is still undefined when you caught an error and did not assign an array to it. Trying to loop over it will then throw an exception that you do not catch.
I guess you're looking for
async function fetchAll() {
try {
const data = await Promise.all(urls.map(url => fetch(url)));
const ext = await Promise.all(data.map(res => res.json()));
for (let item of ext) {
console.log(item);
}
} catch (err) {
console.log(err);
}
}
Instead of fetch(url) on line 5, make your own function, customFetch, which calls fetch but maybe returns null, or an error object, instead of throwing.
something like
async customFetch(url) {
try {
let result = await fetch(url);
if (result.json) return await result.json();
}
catch(e) {return e}
}
if (res.json()==! 'undefined')
Makes no sense whatsoever and is an asynchronous function. Remove that condition and just return res.json():
try {
ext = await Promise.all(data.map(res => res.json()))
} catch (err) {
console.log(err)
}
Whether or not your approach is "best" or "memory efficient" is up for debate. Ask another question for that.
You can have fetch and json not fail by catching the error and return a special Fail object that you will filter out later:
function Fail(reason){this.reason=reason;};
const isFail = o => (o&&o.constructor)===Fail;
const isNotFail = o => !isFail(o);
const fetchAll = () =>
Promise.all(
urls.map(
url=>
fetch(url)
.then(response=>response.json())
.catch(error=>new Fail([url,error]))
)
);
//how to use:
fetchAll()
.then(
results=>{
const successes = results.filter(isNotFail);
const fails = results.filter(isFail);
fails.forEach(
e=>console.log(`failed url:${e.reason[0]}, error:`,e.reason[1])
)
}
)
As for question 2:
Depending on how many urls you got you may want to throttle your requests and if the urls come from a large file (gigabytes) you can use stream combined with the throttle.
async function fetchAll(url) {
return Promise.all(
url.map(
async (n) => fetch(n).then(r => r.json())
)
);
}
fetchAll([...])
.then(d => console.log(d))
.catch(e => console.error(e));
Will this work for you?
If you don't depend on every resource being a success I would have gone back to basics skipping async/await
I would process each fetch individual so I could catch the error for just the one that fails
function fetchAll() {
const result = []
const que = urls.map(url =>
fetch(url)
.then(res => res.json())
.then(item => {
result.push(item)
})
.catch(err => {
// could't fetch resource or the
// response was not a json response
})
)
return Promise.all(que).then(() => result)
}
Something good #TKoL said:
Promise.all errors whenever one of the internal promises errors, so whatever advice anyone gives you here, it will boil down to -- Make sure that you wrap the promises in an error handler before passing them to Promise.all
Regarding question 1, please refer to this:
Handling errors in Promise.all
Promise.all is all or nothing. It resolves once all promises in the array resolve, or reject as soon as one of them rejects. In other words, it either resolves with an array of all resolved values, or rejects with a single error.