How to chain multiple fetch() promises?
Solution 1
The best way to go about this is to use Promise.all()
and map()
.
What map will do in this context return all the promises from fetch
.
Then what will happen is await
will make your code execution synchronous as it'll wait for all of the promise to be resolved before continuing to execute.
The problem with using forEach
here is that it doesn't wait for asynchronous request to be completed before it moves onto the next item.
The code that you should be using here is:
fetch(API_URL_DIARY)
.then(response => response.json())
.then(data => {
console.log("old", data);
return data;
})
.then(async data => {
await Promise.all(data.map((e, index, array) => {
return fetch(API_URL_FOOD_DETAILS + e.foodid)
.then(response => response.json())
.then(data => {
array[index] = {...e, ...data};
console.log("update");
})
}));
console.log("new", data)
});
Solution 2
fetch
is a Promise. This is asyncronous call, so the "new" console.log runs before finished all the promises. Use Promise.all()
for that.
You can do this so:
fetch(API_URL_DIARY)
.then(response => response.json())
.then(data => {
console.log("old", data);
return data;
})
.then(data => {
return Promise.all(data.map(food =>
fetch(API_URL_FOOD_DETAILS + food.foodid)
.then(resp => resp.json())
.then(json => {
// do some work with json
return json
})
))
})
.then(data => console.log('new', data))
Solution 3
You shouldn't be using forEach
here. The best solution is to use Promise.all
which waits for an array of promises (fetch
is a promise) to all resolve, after which you can process the data.
Here I've created a dummy fetch function with some sample data to quickly show you how that works.
const dummyObj = {
main: [ { id: 1 }, { id: 2 }, { id: 5 } ],
other: {
1: 'data1',
2: 'data2',
3: 'data3',
4: 'data4',
5: 'data5',
6: 'data6',
7: 'data7',
}
}
// The summy function simply returns a subset of the sample
// data depending on the type and id params after 2 seconds
// to mimic an API call
function dummyFetch(type, id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(id ? dummyObj[type][id] : dummyObj[type]);
}, 2000);
});
}
// In the first fetch we display the data, just
// like you did in your example
dummyFetch('main')
.then(data => {
console.log("old", data);
return data;
})
.then(data => {
// Instead of a forEach use Array.map to iterate over the
// data and create a new fetch for each
const promises = data.map(o => dummyFetch('other', o.id));
// You can then wait for all promises to be resolved
Promise.all(promises).then((data) => {
// Here you would iterate over the returned group data
// (as in your example)
// I'm just logging the new data as a string
console.log(JSON.stringify(data));
// And, finally, there's the new log at the end
console.log("new", data)
});
});
Here's the async/await
version:
const dummyObj = {
main: [ { id: 1 }, { id: 2 }, { id: 5 } ],
other: {
1: 'data1',
2: 'data2',
3: 'data3',
4: 'data4',
5: 'data5',
6: 'data6',
7: 'data7',
}
}
function dummyFetch(type, id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(id ? dummyObj[type][id] : dummyObj[type]);
}, 2000);
});
}
(async () => {
const oldData = await dummyFetch('main');
console.log("old", oldData);
const promises = oldData.map(o => dummyFetch('other', o.id));
const newData = await Promise.all(promises);
console.log(JSON.stringify(newData));
console.log('new', newData);
})();
Solution 4
Storing multiple responses in a single array
The following code fetches multiple keywords in queries and stores all the response of all three responses to the all array
let queries = ["food", "movies", "news"]
let all = []
queries.forEach((keyword)=>{
let [subres] = await Promise.all([fetch(`https://reddit.com/r/${keyword}/hot.json?limit=100`).then((response) => response.json())]);
all.push(subres)
})
//now you can use the data globally or use the data to fetch more data
console.log(all)
Related videos on Youtube
Username
Updated on September 15, 2022Comments
-
Username over 1 year
The following code fetches a json list and then does another fetch call for each list item to change their values. The problem is that itβs not done synchronously. βnewβ is printed to the console before βupdateβ.
fetch(API_URL_DIARY) .then(response => response.json()) .then(data => { console.log("old", data); return data; }) .then(data => { data.forEach(function(e, index,array) { fetch(API_URL_FOOD_DETAILS + e.foodid) .then(response => response.json()) .then(data => { array[index] = {...e, ...data}; console.log("update"); }) }); console.log("new", data) });
Update
Here's how I incorporated @Andy's solution:
function fetchFoodDetails(id, index) { return fetch(API_URL_FOOD_DETAILS + id) .then(response => response.json()) .then(data => { return [index, data]; }); } function fetchDiary() { return fetch(API_URL_DIARY) .then(response => response.json()) .then(data => { return data; }) } (async () => { const data = await fetchDiary(); console.log("old", JSON.stringify(data)); const promises = data.map((food, index) => fetchFoodDetails(food.id, index)); await Promise.all(promises).then(responses => { responses.map(response => { data[response[0]] = {...data[response[0]], ...response[1]}; console.log("update"); }) }); console.log('new', JSON.stringify(data)); })();
It was more difficult so I went with @connoraworden's solution. But I think it can be simplified.
Thanks for all your answers.
-
Andy almost 5 yearsI think the OP means to have "new" printed after all the update fetches in the
forEach
are completed, so this won't work. -
GBWDev almost 5 yearsIf OP can confirm your suspicion, I'll gladly update my answer @Andy
-
Username almost 5 yearsThe code looks right but
data
is undefined at the end, and "update" is printed after "new". I'm not sure that returning.forEach()
promise works -
Username almost 5 yearsThis is an elegant solution. I learned a lot. Many thanks @connoraworden
-
lgabster almost 5 yearsYou are right, you must wait for all the promise resolved. I edited the answer code
-
Username almost 5 yearsI appreciate the detailed answer, which I upvoted. Though it was more difficult to incorporate with my code. I've added it to my original post so you can see it.