Nested Fetching: A Deep Dive
TLDR;
const getUsers = new Promise((resolve, reject) => {
fetch("http://localhost:3000/users")
.then((res) => res.json())
.then((userIds) => {
Promise.all(
userIds.map((userId) =>
fetch(`http://localhost:3000/user/${userId}`).then((res) =>
res.json()
)
)
).then((users) => resolve(users));
});
});
// How to use our promise:
getUsers.then((usersDetails) => console.log(usersDetails));
I’ve noticed that a lot of people get stuck on nested fetching for Assignment 2 (dw, I was one of them when I did the course), so I’ve made a quick run-down on how to approach this section. You can find relevant tutorials on the course site as well.
You're tasked with generating a list of all users along with their detailed information. Ideally, you want an output that looks somewhat like this:
users: [
{
userId: 0,
name: 'Eric',
DOB: '26/05/1999',
profileImage: 'https://www.youtube.com/watch?v=eBGIQ7ZuuiU',
age: 23
},
{
userId: 1,
name: 'Hayden',
DOB: '26/05/2005',
profileImage: 'https://www.youtube.com/watch?v=eBGIQ7ZuuiU',
age: 12
},
{
userId: 2,
name: 'Soorria',
DOB: '26/05/1912',
profileImage: 'https://www.youtube.com/watch?v=eBGIQ7ZuuiU',
age: 100
}
]
But here's the twist: the server only grants access to two endpoints: /users
and /user/{userId}
.
While /users
offers you:
{
userIds: [0, 1, 2]
}
Accessing /user/0
serves up:
{
userId: 0,
name: 'Eric',
DOB: '26/05/1999',
profileImage: 'https://www.youtube.com/watch?v=eBGIQ7ZuuiU',
age: 23
}
So, for each userId, we need to gather all the detailed information repeatedly.
One might think to fetch the userIds and then loop through each ID, making subsequent fetch calls for user details:
const users = [];
fetch("http://localhost:3000/users")
.then((res) => res.json())
.then((userIds) => {
for (const userId of userIds) {
fetch(`http://localhost:3000/user/${userId}`)
.then((res) => res.json())
.then((user) => users.push(user));
}
});
console.log(users);
But hol up, this would be an empty array! Why? The asynchronous nature of fetch means our console.log doesn't wait for the fetches to complete. (remember you are not allowed to use async
and await
)
Let's bundle our chained fetches in a promise wrapper:
const getUsers = new Promise((resolve, reject) => {
fetch("http://localhost:3000/users")
.then((res) => res.json())
.then((userIds) => {
Promise.all(
userIds.map((userId) =>
fetch(`http://localhost:3000/user/${userId}`).then((res) =>
res.json()
)
)
).then((users) => resolve(users));
});
});
// How to use our promise:
getUsers.then((usersDetails) => console.log(usersDetails));
eaassyyy, all done.
We avoided the issue with the naive approach by waiting for all nested fetches to resolve. Here's a brief breakdown:
We wrapped our operations within an overarching promise, named getUsers
. This promise's sole objective? Return a list of users complete with their details.
We then start with fetching all the userIds inside the overarching promise.
With these IDs, we're ready for the next phase: fetching each user's details. This is where Promise.all
is needed. It waits for the completion of all promises in the sequence, returning in the order they were given. [caution: if even one promise fails (say, a bad userId), the whole Promise.all
will fail]
Once we've received the full details of our users, we wrap things up by resolving our main promise.