How can I call async function inside a loop? - javascript

I have below code in node. In getPosts, it reads 10 posts from database which is an async function call. And for each post, it needs to read user info. from database which is another async function call. How can I make it work in node js?
const getUser = async (userId) => {
// read user from database
}
const getPosts =async () => {
const posts = await getPostsFromDB(10); // get 10 posts from database
for(let i=0; i<posts.length; i++){
posts[i].user = await getUser(posts[i].userId) // ERROR: I can't call await inside a loop
}
}
I am thinking about using Promise.all() like below:
const getPosts =async () => {
const posts = await getPostsFromDB(10); // get 10 posts from database
const allProms = posts.map(post => getUser(post.userId));
Promise.all(allProms); // how can I assign each user to each post?
}
but I don't know how I can assign each user to each post after calling Promise.all().

Consider approaching the problem slightly differently. If you wait for responses in an iterative loop, it'll produce poor performance. Instead, you could push them all into an array and wait for them — so they're all fetching at the same time.
const getUser = async (userId) => {
try {
// read
} catch (e) {
// catch errors
}
// return data
}
const getPosts = async () => {
const posts = await getPostsFromDB(10); // get 10 posts from database
const userRequests = posts.map((post, index) => getUser(post.userId))
const users = await Promise.all(userRequests)
return posts.map((post, index) => {
post.user = users[index]
})
}
If you think you may have duplicate userIds, consider forming a list of users you can reference before calling getUser.

Related

React: How to save value from async function and use later

I have an async function that calls an api getting me the current role of a user. Later on I want to attach that role to a variable. Here's my code
const getRole = async () => {
const response = await roleService.getRole();
const roles = await response.role
return roles
}
...........
const currentRole = getRole() //I want this to be the value from return roles
I'm new to react and having trouble with this. How can I set currentRole to the value in return roles?
I would opt to save the information that you got from the API on a state
const [roles, setRoles] = useState();
const getRole = async () => {
const response = await roleService.getRole();
const roles = await response.role
setRoles(roles);
}
you can call the gerRole function on a useEffect like this
useEffect(() => {
getRole();
}, []);
or you can call the getRole function on a button click
<button onClick={getRole}>Click me to get roles</button>

React useffect infinite render or stale state

I am getting some posts from the server and every time i delete or add a post, i need to refresh the page for it to showcase the changes. This can be solved by adding posts in the dependency array but then an infinite render occurs.
useEffect(() => {
const getUsersData = async () => {
const results = await getUsers();
setUsers(results.data);
console.log(results.data);
};
const getPostData = async () => {
const results = await getPosts();
console.log(results.data);
setPosts(
results.data.sort((p1, p2) => {
return new Date(p2.created_at) - new Date(p1.created_at);
})
);
};
getUsersData();
getPostData();
}, [posts]);
{post.user_id === user.result.user_id && (
<DeleteIcon
color="secondary"
onClick={() =>
handleDelete(post.post_id, { user_id: user.result.user_id })
}
/>
)}
__
export const deletePost = async (postId, userId) => {
await axios.delete(`${URL}/posts/${postId}`, { data: userId });
};
useEffect(() => {
const getPostData = async () => {
...
setPosts(
...
);
};
getPostData();
}, [posts]);
Oops !
The issue is here, you are setting your post each time you... are setting your posts !
Maybe you should use setpost somewhere else in your code ? :)
If you want to update your posts, you should do it in another useeffect, with whatever dependencies you need to know you need to update your poste. Or do a timed refresh, also in a use effect. You can then call setpost, without having access to post. You don't need post as dependency to update it, on the contrary, that's what's causing a loop here :)
You can't add posts as a dependency to your useEffect ince you are using that effect to call setPosts. This will cause an infinite rerender loop.
Your issue is one of the reasons why many fetch libraries for react have been created in the last few years, like react-query, or RTK query, because what you want to do, is to update your queried data, in response to a mutation on the same data-set on server ( mutations: POST, DELETE, PATCH, PUT ). These libraries let you specify what query data to revalidate once you perform a mutation, in your case, you would tell your getPosts query to be reexecuted and revalidated in cache everytime you perform an addPost or deletePost mutation.
If you want to implement manually both optimistic update and cache revalidation, you need to add some more code, you will have basically these code blocks:
const [posts, setPosts] = useState([])
const getUsersData = async () => {
const results = await getUsers();
setUsers(results.data);
console.log(results.data);
};
const getPostsData = async () => {
const results = await getPosts();
console.log(results.data);
setPosts(results.data.sort((p1, p2) => {
return new Date(p2.created_at) - new Date(p1.created_at);
})
);
};
const handleDelete = async (postId, userId) => {
await deletePost(postId, userId) // DELETE Call
const idx = posts.findIndex(p => p.id === postId )
setPosts(posts => [...posts.slice(0, idx),...posts.slice(idx+1, posts.length)] // Optimistic update of UI
getPosts() // Revalidate posts after the delete operation
}
const handleAddPost = async (post, userId) => {
await addPost(post, userId) // POST Call
setPosts(posts => [post,...posts] // Optimistic update of UI
getPosts() // Revalidate posts after the POST operation
}
// Retrieve data on the first component mount
useEffect(() => {
getUsersData();
getPostData();
}, []);

React JS multiple API calls, data undefined or unexpected reserved word 'await' mapping through the data:

I'm creating a JS function that will make a call to an API, loop through the returned data and perform another call to retrieve more information about the initial data (for example where the first call return an ID, the second call would return the name/address/number the ID corresponds to). Positioning the async and await keywords though, have proven to be way more challenging than I imagined:
useEffect(() => {
const getAppointments = async () => {
try {
const { data } = await fetchContext.authAxios.get('/appointments/' + auth.authState.id);
const updatedData = await data.map(value => {
const { data } = fetchContext.authAxios.get('/customerID/' + value.customerID);
return {
...value, // de-structuring
customerID: data
}
}
);
setAppointments(updatedData);
} catch (err) {
console.log(err);
}
};
getAppointments();
}, [fetchContext]);
Everything get displayed besides the customerID, that results undefined. I tried to position and add the async/await keywords in different places, nothing works. What am I missing?
map returns an array, not a promise. You need to get an array of promises and then solve it (also, if your way worked, it would be inefficient waitting for a request to then start the next one.)
const promises = data.map(async (value) => {
const { data } = await fetchContext.authAxios.get('/customerID/' + value.customerID);
return {
...value,
customerID: data
};
});
const updatedData = await Promise.all(promises);

Merging various backend requests in the express res.send()

I'm trying to make several asynchronous backend calls to generate a JSON response in my express API. Because of the nature of the API, I have 3 requests that are being made that are dependent on each other in some way.
Request 1: Returns an Array of values that are used to make request 2. Each value will be used as a mapping for the remaining requests. That is to say, it will be a unique identifier used to map the response from the requests in Request 3.
Request 2 (Parallel Batch): A request is made using each value from the Array returned in request 1. Each of these returns a value to be used in each of the Request 3s. That is to say, it's a 1-to-1
Request 3 (Parallel Batch): This request takes the response from Request 2, and makes a 1-to-1 follow up request to get more data on that specific mapping (the id from request 1)
I would like the final data I send to the consumer to look like this:
{
id1: details1,
id2: details2,
id3: details3,
...
}
Here is the code I have so far...
app.get("/artists/:artist/albums", (req, res) => {
console.log("#############")
const artistName = req.params.artist
let response = {};
let s3Promise = s3.listAlbums(artistName)
let albumDetailsPromises = []
s3Promise
.then((data) => {
data.map((album) => {
// Each album name here will actually be used as the unique identifier for
// the final response
// Build an Array of promises that will first fetch the albumId, then use
// that album id to fetch the details on the album
albumDetailsPromises.push(
discogs.getAlbumId(artistName, album).then( // Returns a promise
({ data }) => {
let masterId = data.results[0].id
let recordName = data.results[0].title
// Storing the album name to carry as a unique id alongside the promise
return [album, discogs.getAlbumDetails(masterId) // Returns a promise ]
}
)
)
})
})
.then(() => {
// When all the albumIds have been fetched, there will still exist a promise in the
// second index of each element in the albumDetailsPromises array
Promise.all(albumDetailsPromises)
.then((namedPromises) => {
namedPromises.map(
(album) => {
let albumName = album[0] // Unique Id
let albumDetailPromise = album[1]
// Resolving the albumDetailsPromise here, and storing the value on
// a response object that we intend to send as the express response
albumDetailPromise
.then(
({ data }) => {
response[albumName] = data
})
.catch(err => response[albumName] = err)
})
})
})
.catch((err) => console.log(err))
})
As of now, everything seems to be working as expected, I just can't seem to figure out how to "await" the response object being updated at the end of all these Promises. I've omitted res.send(response) from this example because it's not working, but that's of course my desired outcome.
Any advice is appreciated! New to javascript...
I would recommend rewriting this using async/await as it helps to reduce nesting. You can also extract the logic the get the album-details into a separate function, as this also increases the readability of the code. Something like this (this still needs error-handling, but it should give you a start):
app.get("/artists/:artist/albums", async (req, res) => {
const artistName = req.params.artist;
const albumNames = await s3.listAlbums(artistName);
const result = {};
const albumDetailPromises = albumNames.map(albumName => requestAlbumDetails(discogs, artistName, albumName));
const resolvedAlbumDetails = await Promise.all(albumDetailPromises);
// map desired response structure
for(const albumDetail of resolvedAlbumDetails) {
result[albumDetail.albumName] = albumDetail.albumDetails;
}
res.json(result);
});
async function requestAlbumDetails(service, artistName, albumName) {
const albumInfo = await service.getAlbumId(artistName, albumName);
const masterId = albumInfo.results[0].id;
const albumDetails = await service.getAlbumDetails(masterId);
return { albumName, albumDetails };
}
To answer your question how you could do it with your code:
You'd need to wait for all details to be fulfilled using another Promise.all call and then just send the response in the then-handler:
Promise.all(albumDetailsPromises)
.then((namedPromises) => {
const detailsPromises = namedPromises.map(
(album) => {
let albumName = album[0];
let albumDetailPromise = album[1];
return albumDetailPromise
.then(({ data }) => {
response[albumName] = data;
})
.catch(err => response[albumName] = err);
});
return Promise.all(detailsPromises)
.then(() => res.json(response));
})
Refactored using async/await...
app.get("/artists/:artist/albums", async (req, res) => {
const artistName = req.params.artist
let response = {};
let albums = await s3.listAlbums(artistName)
const promises = albums.map(async (album) => {
let result = await discogs.getAlbumId(artistName, album)
try {
let masterId = result.data.results[0].id
let tempRes = await discogs.getAlbumDetails(masterId)
return [album, tempRes.data]
} catch (error) {
return [album, { "msg": error.message }]
}
})
responses = await Promise.all(promises)
responses.map(data => { response[data[0]] = data[1] })
res.send(response)
})

NextJS: Waiting for the data from Google Firestore

So, I'm trying to get data from Google Firestore and I'm using NextJS framework.
Index.getInitialProps = async function () {
const db = await loadDB()
let data = []
db.firestore().collection('data').get().then(querySnapshot => {
querySnapshot.forEach(doc => {
data.push(doc.data())
console.log(data)
})
})
return {
data
}
}
So, on my "server" I get the data but in my Index component, it just remains an empty array... anyone know what I'm missing here to get the data to the component?
I'm assuming it's an await somewhere...
If you're wondering where the missing await is supposed to be, there's only one place. It only works with promises, and the get() returns a promise, since the query to Firestore is not immediately complete.
async function () {
const db = await loadDB()
let data = []
const querySnapshot = await db.firestore().collection('data').get()
querySnapshot.forEach(doc => {
data.push(doc.data())
console.log(data)
})
return {
data
}
})

Categories