0

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().

Joey Yi Zhao
  • 37,514
  • 71
  • 268
  • 523
  • Use `Promise.all()` –  May 15 '20 at 12:29
  • Promise.all() will return all users for all posts. How can I assign each user to each post? – Joey Yi Zhao May 15 '20 at 12:30
  • Is this: "ERROR: I can't call await inside a loop" being told to you by the IDE? – Yakko Majuri May 15 '20 at 12:32
  • This should work fine. Is there any way you can create a [small demo](https://stackoverflow.com/help/minimal-reproducible-example) for this to show the issue happening. You don't have to create real ajax call. You can create fake ones using Promise and setTimeout. – palaѕн May 15 '20 at 12:32
  • No it is not IDE, it is javascript syntax error – Joey Yi Zhao May 15 '20 at 12:32
  • 1
    Does this answer your question? [Asynchronous Process inside a javascript for loop](https://stackoverflow.com/questions/11488014/asynchronous-process-inside-a-javascript-for-loop) – Liam May 15 '20 at 12:33
  • `const users = await Promise.all(...);` and `posts.forEach((post, i) => post.user = users[i]);` –  May 15 '20 at 12:34
  • If you are concerned about failing `getUser` calls you need to construct your own promise and handle the failure accordingly so the outer Promise always resolves, then pass the outer promises array to Promise.all() –  May 15 '20 at 12:38
  • Here's example code to illustrate: https://jsfiddle.net/khrismuc/wjpmeo8c/ –  May 15 '20 at 12:48

1 Answers1

0

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.

Shakespeare
  • 1,286
  • 9
  • 15
  • what if there is an error in `users`. Is the `users[index]` an error or the length of `users` array is less than posts? – Joey Yi Zhao May 15 '20 at 12:34
  • If it fails to get one of the users, its value will be undefined in the `users` array, so you'd want to change the bottom map to accommodate that. This does mean that the `posts` array and `users` array will always be the same length, though. – Shakespeare May 15 '20 at 12:36
  • On a completely unrelated note, I'd use `.forEach` instead of `.map` if nothing gets actually mapped –  May 15 '20 at 12:37
  • Fair comment — I tend to use map for syntax sugar, but I'd be OK with `forEach` too for the reasons you said. – Shakespeare May 15 '20 at 12:39