How do I combine two fetch request into the same array? - javascript

I am trying to combine two fetch requests in one call so I can get all the data in the same array.
I have tried the Promise.all method but I don't know if it is the right way to do it.
getWeather = async (e) => {
e.preventDefault();
const city = e.target.elements.city.value;
//const api_call = await
const promises = await Promise.all([
fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&APPID=${API_KEY}`),
fetch(`http://api.openweathermap.org/data/2.5/forecast?q=${city}&units=metric&APPID=${API_KEY}`)
])
const data = promises.then((results) =>
Promise.all(results.map(r => r.text())))
.then(console.log)
The code actually works and I'm getting data back but I can't understand the json response.
(2) ["{"coord":{"lon":-5.93,"lat":54.6},"weather":[{"id"…7086601},"id":2655984,"name":"Belfast","cod":200}", "{"cod":"200","message":0.0077,"cnt":40,"list":[{"d…on":-5.9301},"country":"GB","population":274770}}"]
How should I set the state?
My state was set like this, with only one call.
if (city) {
this.setState({
temperature: data[0].main.temp,
city: data[0].name,
Is there a better way to do it?

I'd do:
getWeather = async (e) => {
e.preventDefault();
const fetchText = url => fetch(url).then(r => r.json()); // 1
const /*2*/[weather, forecast] = /*3*/ await Promise.all([
fetchText(`.../weather`),
fetchText(`.../forecast`)
]);
this.setState({ temperature: weather.temp, /*...*/ });
}
1: By using a small helper, you don't have to call Promise.all twice. With this both requests are done in parallel (and you should use .json() as you want to parse it as JSON).
2: Through array destructuring you can easily get back the promises results.
3: Through awaiting you get the actual benefit from async functions: You don't need nested .then chains

You can write in following way which is cleaner approach and will have your data categorised
const success = res => res.ok ? res.json() : Promise.resolve({});
const weather = fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&APPID=${API_KEY}`)
.then(success);
const forecast = fetch(`http://api.openweathermap.org/data/2.5/forecast?q=${city}&units=metric&APPID=${API_KEY}`)
.then(success);
return Promise.all([weather, forecast])
.then(([weatherData, forecastData]) => {
const weatherRes = weatherData;
const ForecastRes = forecastData; // you can combine it or use it separately
})
.catch(err => console.error(err));
}

Related

How to concat multiple responses and set all response in an array [React JS]

I am doing an API call which is returning IDs and based on number of ids I am doing another call and trying to combine the responses but I am stuck with async issues.
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
await ids.forEach(async (id) => {
const result = await getUserInfo(id);
setRNOUsers(...result);
// combine result in one state
});
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers); // this is empty and runs even before callng api
}, []);
How can handle this?
You can use Promise.all to wait for all responses, and then set them together with setRNOUsers
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
const responses = await Promise.all(ids.map(id => getUserInfo(id)))
setRNOUsers(...responses.flatMap(x => x));
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers);
}, []);
Side note, the problem with console.log('RNOUsers', RNOUsers) is setRNOUsers (initialized by useState) is asynchronous. Besides that, your API calls are also asynchronous, so you cannot get values from RNOUsers immediately in useEffect. If you want to see data in that log, you should wait until the state is updated and your component gets re-rendered with your latest data.

React: useState(): Fetching data during an ongoing fetch does not work

I want to display a list of the New York Times bestsellers by fetching the data via an API. But, the covers' links have to be fetched from the Google Books API, since the NYT API does not provide them.
Therefore, I want to map a list with the fetched data. In the same step in which I fetch the data from the NYT API, the data from the Google Books API shall be fetched, too. So, I use the await keyword to let the code wait for the second fetch to be finished.
But, unfortunately, the list does not update though the promise has been fulfilled, and the data has been fetched. The bestsellerlist variable is an array of fulfilled promises, but no "real array" I could access.
I guess it should be possible to split the fetch, and create the list out of two arrays: One with the NYT API data, and one with the Google Books API data. Nevertheless, I wonder how I can fix the code, so I can fetch all the data and create the list in one step.
const fetchLinkCover = async (isbn) => {
const res = await fetch(googleApi(isbn));
const json = await res.json();
return json.items[0].volumeInfo.imageLinks.smallThumbnail;
};
useEffect(() => {
fetch(nytimesApi)
.then((res) => res.json())
.then((json) => {
const fetchedBookList = json.results.books;
setBestsellerList(
fetchedBookList.map(async (item) => {
const linkCover = await fetchLinkCover(item.isbns[0].isbn13);
return {
title: item.title,
authors: item.author,
ranking: item.rank,
cover: linkCover,
};
})
);
});
}, []);
console.log(bestsellerList);
console after executing the code
.map doesn't do anything special with promises. If you return a promise from your map function (and async functions always return promises), then .map will create an array of promises. It won't wait for those promises to resolve.
To wait for an array of promises, you can use Promise.all to combine them together into a single promise. Then you either call .then on that promise, or you put your code in an async function and await it.
With .then:
fetch(nytimesApi)
.then((res) => res.json())
.then((json) => {
const fetchedBookList = json.results.books;
const promises = fetchedBookList.map(async (item) => {
const linkCover = await fetchLinkCover(item.isbns[0].isbn13);
return {
title: item.title,
authors: item.author,
ranking: item.rank,
cover: linkCover,
};
});
return Promise.all(promises);
})
.then((books) => {
setBestsellerList(books);
});
Or with async/await:
useEffect(() => {
const fetchBooks = async () => {
const res = await fetch(nytimesApi);
const json = await res.json();
const fetchedBookList = json.results.books;
const promises = fetchedBookList.map(async (item) => {
const linkCover = await fetchLinkCover(item.isbns[0].isbn13);
return {
title: item.title,
authors: item.author,
ranking: item.rank,
cover: linkCover,
};
});
const books = await Promise.all(promises);
setBestsellerList(books);
};
fetchBooks();
}, []);

Returning results in their order from multiple api queries

I want to make multiple API calls from an array of values. I would like to resolve the first call, then the second, third, and so on in the order in which the API request receives the data. everything I've tried gets me to the point of logging the final data in the console or resolving the data but not necessarily returning the array of data in the format with which the requests were made. Below is one of the solutions I was recently playing with. It definitely does not work (and there are a lot of issues with it) but I do see the unresolved data in the console. The console.logs just show the points where some sort of accurate data is returned as an unresolved promise
Function
const sendTrackingData = (storeKey, deliveries) => {
const trackingUrl = deliveries.map(async (delivery) => {
const urlData = []
delivery.parcels.map(async (parcel) => {
const {trackingData} = parcel;
const carrier = trackingData.carrier.toLowerCase();
const trackingCode = trackingData.trackingId;
urlData.push(await getTrackingUrl(storeKey, carrier, trackingCode))
})
console.log(urlData)
return urlData
})
return Promise.all(trackingUrl)
};
It is called here for now in a use effect
const test = await sendTrackingData(storeKey, deliveries)
console.log(test,'the test url data =========>')
API call
export const getTrackingUrl = (storeKey, carrier, trackingCode) => {
return axios.post(`random-url-address/returnedData`, {
storeKey,
carrier,
trackingCode,
});
};
You need to call Promise.all on the main asynchronous requests - the getTrackingUrl - for the order to be preserved:
const sendTrackingData = (storeKey, deliveries) => (
Promise.all(deliveries.map((delivery) => (
Promise.all(delivery.parcels.map((parcel) => {
const { trackingData } = parcel;
const carrier = trackingData.carrier.toLowerCase();
const trackingCode = trackingData.trackingId;
return getTrackingUrl(storeKey, carrier, trackingCode);
}))
)))
);
When you want to preserve order, .push after an asynchronous request is almost never the right approach.
you can try
delivery.parcels.map(async (parcel , index) => {
const {trackingData} = parcel;
const carrier = trackingData.carrier.toLowerCase();
const trackingCode = trackingData.trackingId;
urlData.splice(index, 0, await getTrackingUrl(storeKey, carrier, trackingCode));
})
console.log(urlData)
return urlData

For loop with fetch returning empty array

I'm writing a server route that makes api calls.
I need to make two different fetch requests cause I need more info that's coming in the first fetch.
The problem is that I'm declaring a variable out of the promise scope and for some reason, my res.send is not awaiting until the array gets full.
I need to iterate until result 9 (I can't use theDogApi's predefined filters to show nine results!)
if (req.query.name) {
var myRes = [];
fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`)
.then(r => r.json())
.then( data => {
for (let i = 0; i < 8 && i < data.length; i++) {
fetch(`https://api.thedogapi.com/v1/images/${data[i].reference_image_id
}`)
.then(r => r.json())
.then(datos => {
myRes.push({ ...data[i], ...datos });
})
}
})
.then(res.send(myRes))
}
I'll appreciate the help!
You can try using Promise.all to turn your array of fetch calls into an aggregate promise that resolves to an array of responses when all have arrived. If any fail, the whole thing fails (use Promise.allSettled if you don't want all-or-nothing semantics). Don't forget to catch the error.
Although the code doesn't show it, be sure to check response.ok to make sure the request actually succeeded before calling .json(). Throwing an error if !repsonse.ok and handling it in the .catch block is a typical strategy. Writing a wrapper on fetch is not a bad idea to avoid verbosity.
Lastly, note that Array#slice replaces the for loop. For arrays with fewer than 8 elements, it'll slice as many as are available without issue.
// mock everything
const fetch = (() => {
const responses = [
{
json: async () =>
[...Array(10)].map((e, i) => ({reference_image_id: i}))
},
...Array(10)
.fill()
.map((_, i) => ({json: async () => i})),
];
return async () => responses.shift();
})();
const req = {query: {name: "doberman"}};
const key = "foobar";
const res = {send: response => console.log(`sent ${response}`)};
// end mocks
fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`)
.then(response => response.json())
.then(data =>
Promise.all(data.slice(0, 8).map(e =>
fetch(`https://api.thedogapi.com/v1/images/${e.reference_image_id}`)
.then(response => response.json())
))
)
.then(results => res.send(results))
.catch(err => console.error(err))
;
Here is an example of an async function unsing await:
async function fun(queryName, key){
const a = [], p, j = [];
let firstWait = await fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`);
let firstJson = await firstWait.json(); // must be an Array
for(let i=0,n=8,j,l=firstJson.length; i<n && i<l; i++){
a.push(fetch('https://api.thedogapi.com/v1/images/'+firstJson[i].reference_image_id));
}
p = await Promise.all(a);
for(let v of p){
j.push(v.json());
}
return Promise.all(j);
}
// assumes req, req.query, req.query.name, and key are already defined
fun(req.query.name, key).then(a=>{
// a is your JSON Array
});
JSON
Here's my hot take: Stop using low-level functions like fetch every time you want to get JSON. This tangles up fetching logic every time we want to get a bit of JSON. Write getJSON once and use it wherever you need JSON -
const getJSON = s =>
fetch(s).then(r => r.json())
const data =
await getJSON("https://path/to/some/data.json")
// ...
URL and URLSearchParams
Another hot take: Stop writing all of your URLs by hand. This tangles URL writing/rewriting with all of your api access logic. We can setup a DogApi endpoint once, with a base url and an apikey -
const DogApi =
withApi("https://api.thedogapi.com/v1", {apikey: "0xdeadbeef"})
And now whenever we need to touch that endpoint, the base url and default params can be inserted for us -
const breed =
// https://api.thedogapi.com/v1/breeds/search?apikey=0xdeadbeef&name=chihuahua
await getJSON(DogApi("/breeds/search", {name}))
// ...
withApi has a simple implementation -
const withApi = (base, defaults) => (pathname, params) =>
{ const u = new URL(url) // <- if you don't know it, learn URL
u.pathname = pathname
setParams(u, defaults)
setParams(u, params)
return u.toString()
}
function setParams (url, params = {})
{ for (const [k,v] of Object.entries(params))
url.searchParams.set(k, v) // <- if you don't know it, learn URLSearchParams
return url
}
fruits of your labor
Now it's dead simple to write functions like imagesForBreed, and any other functions that touch JSON or your DogApi -
async function imagesForBreed (name = "")
{ if (name == "")
return []
const breed =
await getJSON(DogApi("/breeds/search", {name}))
const images =
data.map(v => getJSON(DogAPI(`/images/${v.reference_image_id}`))
return Promise.all(images)
}
And your entire Express handler is reduced to a single line, with no need to touch .then or other laborious API configuration -
async function fooHandler (req, res)
{
res.send(imagesForBreed(req.query.name))
}

React - Returning data from API

I know there are similar questions, but I can't find the answer.
First, please tell me if I'm doing something really wrong
I need to populate my state with data from an API call. This was working fine with code above:
export const GetPlanets = async () => {
const planets = await axios.get(`${BASE_URL}`).catch((e) => {
console.error(e);
})
return planets.data.results
}
But then I needed to make a second call to several links from one json response filed, and I managed to make it work (don't know if it is the correct approach, though)
const GetPlanets = async () => {
let planetas = {}
await axios.get(`${PLANETS_URL}`)
.then((p) => {
planetas = p.data.results
return axios.get(`${FILMS_URL}`)
}).then((f) => {
planetas.films.forEach((v, i) => {
planetas[i].film = f
})
})
})
.catch((e) => {
console.error(e);
})
return planetas
}
This is my component file, where I try to get the object, like I was doing before
useEffect(() => {
const fetchPlanetas = async () => { // ME TRYING...
const planetas = await GetPlanets()
setPlanetas(planetas)
setToShow(planetas[0])
};
fetchPlanetas()
}, [])
But all I get is undefined
You're getting an array of undefined because .map() needs a return value. In both your .map() callbacks, you are not returning anything.
const results = [1, 2, 3, 4]
const results2 = results.map(elem => {
elem = elem + 1
})
console.log(results2)
But, even if you did return something in your .map() callback, GetFilms(f) is asynchronous, so you would not get the results of GetFilms() mapped into the array as you would expect.
You have a couple of options:
If you have access to the API, send the films data along with the rest of the data when you do your first request.
Use async/await and Promise.all() to get responses.

Categories

Resources