Promise.all() and catching errors - javascript

I wrote a snippet of code that gets some a JSON from the Foursquare API. From this JSON, I get IDs of venues. These IDs are then used to get more details from those specific venues by issuing a fetch() request for every ID, and mapping those requests in an array. That array is then passed into Promise.all(). When the API is available, everything works, but it's the error catching that I can't get my head around.
fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
.then(response => response.json())
.then(data => {
const venueIds = data.response.venues.map(venue => venue.id)
const venuePromises = venueIds.map(venueId => {
fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
.then(response => {
// Must check for response.ok, because
// catch() does not catch 429
if (response.ok) {
console.log('ok')
return response.json()
} else {
Promise.reject('Error when getting venue details')
}
})
})
Promise.all(venuePromises).then(data => {
const venues = data.map(entry => entry.response.venue) // Error for this line
this.parseFsqData(venues)
}).catch((e) => {console.log(e); getBackupData()})
}).catch((e) => {console.log(e); getBackupData()})
function getBackupData() {
console.log('backup')
}
When the API is not available, I get the following console errors (and more of the same):
TypeError: Cannot read property 'response' of undefined
at MapsApp.js:97
at Array.map (<anonymous>)
at MapsApp.js:97
backup
api.foursquare.com/v2/venues/4b7efa2ef964a520c90d30e3?client_id=ANDGBLDVCRISN1JNRWNLLTDNGTBNB2I4SZT4ZQYKPTY3PDNP&client_secret=QNVYZRG0JYJR3G45SP3RTOTQK0SLQSNTDCYXOBWUUYCGKPJX&v=20180323:1 Failed to load resource: the server responded with a status of 429 ()
Uncaught (in promise) Error when getting venue details
I don't understand why then() after Promise.all() is entered, because response is never ok (there is no ok logged in console). Also, I don't understand why the console.log()'s in the catch() blocks aren't executed, or why they are empty. I don't see any caught error information in console, but still the getBackupData function is called. Finally, it is unclear why the last message in console indicates that the error is uncaught, as I expected reject() to make Promise.all() fail.
How can I tactfully catch any errors (included those not normally caught by catch(), such as 429 errors) and call getBackupData when any errors occur?

Your issues are related: namely, the Promise chain must be returned. If you do not return the Promise, you disconnect any of the caller's Promise#catch handling, and any errors in your Promise / then code will result in unhandled promise rejection errors, such as what you have obtained:
Uncaught (in promise) Error when getting venue details
This uncaught promise rejection appears in your code that handles the resolution of fetch:
if (response.ok) {
console.log('ok')
return response.json()
} else {
Promise.reject('Error when getting venue details') // <----
}
Since this code is being used to construct your venuePromises array, its return value will populate venuePromises. If the response was ok, that array element will have the response JSON from return response.json(). If the response failed, there is no return statement that executes, so the array element has the value undefined. Thus, venuePromises would look like this:
[
{ /** some object for successful response */ },
undefined,
{ /** some other object */ },
...
]
Thus when this array is accessed by your Promise.all's success handler, you get the TypeError since you expected all elements of venuePromises to be valid. This TypeError is caught by the Promise.all's .catch handler (which is why it is logged, and you receive the "backup" text in your log).
To fix, you need to return the Promise.reject, and also the Promise.all. Note that there are some cases of implicit return, but I find it nicer to be explicit, especially if the statement spans multiple lines. Since you're returning the Promise.all statement, you can offload its .then and .catch to the caller, resulting in one less nesting level, and one fewer duplicated .catch handler.
fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
.then(response => response.json())
.then(jsonData => {
const venueIds = jsonData.response.venues.map(venue => venue.id);
const venuePromises = venueIds.map(venueId => {
let link = `https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`;
return fetch(link).then(response => {
// Must check for response.ok, because catch() does not catch 429
if (response.ok) {
console.log('ok');
return response.json();
} else {
console.log(`FAILED: ${link}`);
// Return a Promise
return Promise.reject(`Error when getting venue details for '${venueId}'`);
}
});
});
return Promise.all(venuePromises);
})
.then(venueData => {
const venues = venueData.map(entry => entry.response.venue);
this.parseFsqData(venues);
})
.catch(e => {console.log(e); getBackupData()});
function getBackupData() {
console.log('backup')
}

Try returning the rejected promise.
return Promise.reject('Error when getting venue details')

When working with promises you should return inner promises instad of working with inner "thens".
Check this:
fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
.then(response => response.json())
.then(data => {
const venueIds = data.response.venues.map(venue => venue.id);
const venuePromises = venueIds.map(venueId => {
fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
.then(response => {
// Must check for response.ok, because
// catch() does not catch 429
if (response.ok) {
console.log('ok')
return response.json()
} else {
return Promise.reject('Error when getting venue details')
}
})
});
return Promise.all(venuePromises)
})
.then(venueValues => {
const venues = venueValues.map(entry => entry.response.venue); // Error for this line
this.parseFsqData(venues);
})
.catch((e) => {console.log(e); getBackupData()})
function getBackupData() {
console.log('backup')
}
When returning Promise.all as a value, you're returning a promise so that you can chain further "then" callbacks. The last catch shall capture all promise rejects.
You're also missing the return in the else clause
Hope this helps

I believe the solution is fairly simple; The response of the nested fetch method is missing a return statement. You should get rid of that mysterious error once it is in place.
const venuePromises = venueIds.map(venueId => {
<missing return statement here> fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
.then(response => {

Related

Can't break promise chain with return

I've got a chained promise using .then(), the issues I am having is i'm getting Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client - I know why i'm getting it (there are 2 responses in the promise) however after doing lots of reading I can't figure out how to break the .then() callback - i've been trying to use return, however it keeps running to the next chain in the promise.
How do I stop the chain at the first res - response, when the checkResult if statement is false?
Promise Chain
await query1()
.then((checkResult) => {
if (!checkResult) {
return res.status(200)
}
})
.then(() => query2())
.then((result) => {
return res.status(201).json({ result: result, status: 201 });
})
.catch((err) => console.error(`Error occurred: ${err}`));
Query 1 Example
async function query1(){
return false
}
Just use async/await which simplifies control flow issues like these:
try {
let checkResult = await query1();
if (!checkResult) {
return res.status(200);
}
let result = await query2();
return res.status(201).json({ result: result, status: 201 });
} catch(err) {
console.error(`Error occurred: ${err}`));
}

Can't call fetch api multiple times

I want to call this api multiple times in my project and when I am calling it , It continues giving an error which is
TypeError: Failed to execute 'json' on 'Response': body stream already
read at main.js:Line number
My Code is as Follows
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
a.then((data) => {
return data.json()
}).then((apidata) => {
console.log(apidata)
return apidata
}).catch((error) => {
console.log(error)
})
a.then((fetchdata) => {
return fetchdata.json()
}).then((readingData) => {
console.log(readingData)
}).catch((err) => {
console.log(err)
})
You're not calling fetch multiple times. You're calling it once, and then trying to read the response body multiple times. That's why the error says you're trying to read the body when the stream is already closed — it was closed when you were done reading it the first time.
If you want to use the data twice, store it somewhere and use it twice.
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
a.then((data) => {
return data.json()
}).then((apidata) => {
// **************** Use it twice here
}).catch((error) => {
console.log(error)
})
If you want to fetch it again because it may have been updated, call fetch again:
let thisIsUrl = 'https://api.covid19api.com/summary';
fetch(thisIsUrl)
.then((data) => {
return data.json();
}).then((apidata) => {
console.log(apidata);
return apidata;
}).catch((error) => {
console.log(error);
});
// Presumably this is later (in time), not immediately after the above
fetch(thisIsUrl)
.then((fetchdata) => {
return fetchdata.json();
}).then((readingData) => {
console.log(readingData);
}).catch((err) => {
console.log(err);
});
Finally, this seems unlikely, but if you really want to fetch it once and use that one result in multiple places via the promise chain, keep the promise from then rather than the promise from fetch:
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
.then((data) => {
return data.json()
});
a.then((apidata) => {
// ***** Use it here
}).catch((error) => {
console.log(error)
})
a.then((readingData) => {
// ***** And then again here
}).catch((err) => {
console.log(err);
});
Side note: Your code is falling prey to a footgun in the fetch API; I've written about it in this blog post. fetch only rejects its promise on network errors, not HTTP errors. You have to check for those yourself in the first fulfillment handler, by checking for ok on the response object:
fetch("/your/resource")
.then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status); // Or better, use an Error subclass
}
return response.json();
})
// ...
fetch returns Promise, generally, promises have something like state inside themself;
pending: initial state, neither fulfilled nor rejected.
fulfilled: meaning that the operation was completed successfully.
rejected: meaning that the operation failed.
(source)
So when we call them and get the value from them with then, catch and etc. then they change the state after that call. So here, when you read the value with a.then(…, the promise changes its state to fulfilled and you are not able to call it again, you need a new and fresh Promise, actually a new instance of the fetch.
I want to recommend you to use Promise.all().
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
.then((data) => {
return data.json()
}).then((apidata) => {
console.log(apidata)
return apidata
}).catch((error) => {
console.log(error)
})
Promise.all([a,a,a]);
.then(results => {
// Results are here.
});

Fetch returns promise when I expect an array

I am doing a simple api get request, but I can't seem to isolate the array on its own. its always inside of a promise and I'm not sure how to remove it or how to access the values stored in the array.
function getLocation(name) {
let output = fetch(`http://dataservice.accuweather.com/locations/v1/cities/search?apikey=oqAor7Al7Fkcj7AudulUkk5WGoySmEu7&q=london`).then(data => data.json());
return output
}
function App() {
var output = getLocation(`london`);
console.log (output)
...
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Array(3)
is what is displayed in the console.log I require just the Array(3)
fetch, and Promise#then, always return a promise. To access the information it fetches, consume the promise:
getLocation()
.then(data => {
// use the data
})
.catch(error => {
// Handle/report the error
});
or in an async function:
const data = await getLocation();
Side note: Your getLocation has a very common error (so common I wrote it up on my anemic little blog): It doesn't check that the HTTP operation succeeded. fetch only fails on network errors, not HTTP errors. To fix it:
function getLocation(name) {
return fetch(`http://dataservice.accuweather.com/locations/v1/cities/search?apikey=oqAor7Al7Fkcj7AudulUkk5WGoySmEu7&q=london`)
.then(response => {
if (!response.ok) {
throw new Error("HTTP error, status = " + response.status);
}
return response.json();
});
}

fetch response.json() and response.status

Is this the only way to use the body.json() and also get the status code?
let status;
return fetch(url)
.then((response => {
status = response.status;
return response.json()
})
.then(response => {
return {
response: response,
status: status
}
});
This doesn't work as it returns a promise in the response field:
.then((response)=> {return {response: response.json(), status: response.status}})
Your status is not visible in the second then. You can just get the two properties in the single then.
json() returns a new Promise to you, so you need to create your object inside the then of the result of that function. If you return a Promise from a function, it will be fulfilled and will return the result of the fulfillment - in our case the object.
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(r => r.json().then(data => ({status: r.status, body: data})))
.then(obj => console.log(obj));
The .json method returns a promise, not the parsed value itself. If you want to access both the response and the parsed value in the same callback, you'll need to use nested functions like this:
fetch(url)
.then(response => {
response.json().then(parsedValue => {
// code that can access both here
})
});
Alternatively, you can use await inside an asynchronous function to eliminate the need for callbacks.
const response = await fetch(url);
const parsedValue = await response.json();
// code that can access both here
Of course, you'll want to check for errors, either with a .catch(...) call on a Promise or with a try...catch block in an async function. You could make a function that handles JSON and error cases, and then reuse it for all fetches. For example, something like this:
function handle(response) {
if (response.ok) {
return response.json().then(parsedValue => {
// the status was ok and the body could be parsed
return Promise.resolve({ response, parsedValue });
}).catch(err => {
// the status was ok but the body was empty or not JSON
return Promise.resolve({ response });
});
} else {
return response.json().catch(err => {
// the status was not ok and the body was unobtainable/empty/not JSON
throw new Error(response.statusText);
}).then(parsedValue => {
// the status was not ok and the body was JSON
throw new Error(parsedValue.error.message); // assuming an error message is returned by our REST API
});
}
}
I don't think it's the best design pattern, but hopefully this clarifies how the fetch API works.
PS: I avoided naming any variable or property json since that is the name of the text format. Once it's been parsed, it is no longer JSON.
Using two 'then's seem unnecessary to me.
async/await could get the job done pretty easily.
fetch('http://test.com/getData')
.then( async (response) => {
// get json response here
let data = await response.json();
if(data.status === 200){
// Process data here
}else{
// Rest of status codes (400,500,303), can be handled here appropriately
}
})
.catch((err) => {
console.log(err);
})
Did you try this?
return fetch(url)
.then((r)=> {return {response: r.json(), status: r.status}})
I think the cleanest way is to create a Promise.all() with the pieces you need.
.then(response => Promise.all([Promise.resolve(response.ok), response.text()]))
Which can be written shorter as
.then(response => Promise.all([response.ok, response.text()]))
The promise returns an array with all of the results
.then(data => ({ status: data[0], response: data[1] }))

Returning an Axios Promise from function

Can someone please explain why returning an Axios promise allows for further chaining, but returning after applying a then()/catch() method does not?
Example:
const url = 'https://58f58f38c9deb71200ceece2.mockapi.io/Mapss'
function createRequest1() {
const request = axios.get(url)
request
.then(result => console.log('(1) Inside result:', result))
.catch(error => console.error('(1) Inside error:', error))
return request
}
function createRequest2() {
const request = axios.get(url)
return request
.then(result => console.log('(2) Inside result:', result))
.catch(error => console.error('(2) Inside error:', error))
}
createRequest1()
.then(result => console.log('(1) Outside result:', result))
.catch(error => console.error('(1) Outside error:', error))
createRequest2()
.then(result => console.log('(2) Outside result:', result))
.catch(error => console.error('(2) Outside error:', error))
<script src="https://unpkg.com/axios#0.16.1/dist/axios.min.js"></script>
https://jsfiddle.net/nandastone/81zdvodv/1/
I understand that Promise methods should return a value to be chained, but why is there a difference between these two return methods?
Your first example returns the original promise. Your second example returns a different promise, the one created by calling catch.
The critical differences between the two are:
In your second example, you're not passing on the resolution value, so the promise returned by your then is resolved with undefined (the return value of console.log).
In your second example, you're converting rejections into resolutions with undefined (by returning the result of console.log out of catch). A catch handler that doesn't throw or return a promise that's rejected converts a rejection into a resolution.
One of the key things about promise chains is that they transform the result; every call to then or catch creates a new promise, and their handlers can modify what's sent downstream as the result passes through them.
The usual pattern would indeed be to return the result of the chain, but for the functions in the chain to either intentionally transform the result or pass it on. Normally, you wouldn't have a catch handler except at the terminal end of the chain, unless you're using it to correct the error condition (intentionally converting a rejection into a resolution).
If you wanted to just log what passed through while still allowing callers to see it but did want to return the result of the chain for whatever reason, you'd do this:
return request
.then(result => { console.log(result); return result; })
.catch(error => { console.error(error); return Promise.reject(error); });
or using throw:
return request
.then(result => { console.log(result); return result; })
.catch(error => { console.error(error); throw error; });

Categories