TKX
TKX

Reputation: 71

Cannot figure out how to wait for Promise

I have an array with user IDs and I need to find out what name belongs to each ID and return them in an array. I can get the user's name from the database, using knex and push them into an array, but when I try to send the data it is always an empty array.

I am not really good with Promises so can't figure out how to apply to my project.

const userId = [10,11,12,13]
let users = []

userId.map(id => {
    db.select('name').from('users').where('user_id', id)
    .then(user => {
        users.push(user)
    })
})
res.json(users)

I want the response to wait until the looping finishes and send the users array.

Upvotes: 1

Views: 533

Answers (4)

ilmiacs
ilmiacs

Reputation: 2576

You want Promise.all(), see here.

Try

const userId = [10,11,12,13]

let users = userId.map(id => new Promise(resolve => {
    db.select('name').from('users').where('user_id', id)
    .then(user => {
        resolve(user)
    })
}))
Promise.all(users).then(()=>res.json(users))

Here users is an array of promises. As soon as all of them are resolved, do res.json(users).

Upvotes: 0

Carlos Sá
Carlos Sá

Reputation: 622

First you need to wait for all promises to finish before running res.json(...)

Second, you shouldn't mutate outside variables after promise resolving (the order by which the promises resolve will alter your output and that is not nice.

Something like this should work fine

const userId = [10,11,12,13]

// map userId array to promise array
// Promise.all aggregates a promise array into one big promise that resolves when all promises resolve (and preserves array order)
Promise.all(
  userId.map(id =>
    db
      .select("name")
      .from("users")
      .where("user_id", id)
  )
)
  .then(users => res.json(users))
  .catch(e => console.error("Error::", e));

/*handle error in the catch block*/

/* visual example of Promise.all.then block
Promise.all([           users = [
   getUser(10),    ->     {userId: 10, ....}
   getUser(11),    ->     {userId: 11, ....}
   getUser(12)     ->     {userId: 12, ....}
])                      ]

*/

Upvotes: 1

James
James

Reputation: 82096

As an alternative answer, here's how you could make 1 trip to the DB for this particular query meaning you don't need to wait for multiple Promises and reduce the load on your DB

knex.raw(
  'select name from users where user_id in (' + userId.map(_ => '?').join(',') + ')', 
  [...userId]
);

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074208

Your map is creating an array of undefined because your callback function doesn't return anything. If we tweak it slightly, it'll create an array of promises, which conveniently is exactly what Promise.all expects. :-) So:

const userId = [10,11,12,13]
Promise.all(
    userId.map(id => db.select('name').from('users').where('user_id', id))
)
.then(users => {    // `users` is an array of users, in the same order as the IDs
    res.json(users);
})
.catch(error => {
    // Render an error response
});

Upvotes: 3

Related Questions