2

I'm developing a small card game web app. I'm using a "Rooms" system to hold each game and its players. Each room has a list of players, and I need to filter this list to only get a list of players that have a value in the Firebase Realtime DB. In this case, I'm checking their user presence in /status/{userId}.

I thought I would be able to piece this together fairly simply, but the filtered players array, or activePlayers, is getting read before the filter finishes.

To demonstrate with a forEach:

function debugActivePlayers (players) {
  activePlayers = []
  players.forEach(player => {
   firebase.ref('/status/' + player)
     .once('value')
     .then(snapshot => {
       console.log(snapshot.val()) // 'offline' or 'online' accordingly
       if (snapshot.val() === 'online') activePlayers.push(player)
       return null // <-- IDE throws a fit otherwise lol
     })
  })
  console.log(activePlayers)
}

What I expected to happen was:

offline
online
online
[userId1, userId2, userId3]

But what really happened was:

[]
offline
online
online

How do I make sure the players.forEach (or players.filter) runs synchronously even with async/promise logic inside (the firebase query)?

bugflug
  • 73
  • 2
  • 8

1 Answers1

2

Try using "async/await" like below. This will wait for all asynchronous calls to get completed and then move forward to last console.log

async function debugActivePlayers (players) {
  let activePlayers = []

  for (let player of players) {
    let snapshot = await firebase.ref('/status/' + player).once('value')
    console.log(snapshot.val()) // 'offline' or 'online' accordingly
    if (snapshot.val() === 'online') activePlayers.push(player)
    return null // <-- IDE throws a fit otherwise lol
  }

  console.log(activePlayers)
}

EXPLAINATION - "for..of" with "await makes the execution of remaining statement on hold until all promises are resolved.. To demonstrate that pls test below

function getValue(p) { return Promise.resolve(p) } 

async function test (players = [1,2,3]) {

  for (let player of players) {
    console.log(await getValue(player))
  }

  console.log('done')
}

test()
Nitish Narang
  • 4,124
  • 2
  • 15
  • 22
  • not sure why this is any different than the code he's already using... the console.log is not async... – Pari Baker Nov 13 '18 at 18:53
  • Yes it looks console.log is not async, but "for..of" with "await" makes remaining execution wait until every promise is resolved. – Nitish Narang Nov 13 '18 at 18:55
  • @PariBaker added explanation with example for this. Pls have a look. – Nitish Narang Nov 13 '18 at 19:00
  • 1
    I can't believe I missed something so simple. See my original post for my final code that I just posted (or am about to). – bugflug Nov 13 '18 at 19:05
  • function debugActivePlayers(players) { let activePlayers = players.map(player => { return firebase .ref("/status/" + player) .once("value") .then(snapshot => { console.log(snapshot.val()); if (snapshot.val() === "online") { return player; } else { return; } }); }); } – Pari Baker Nov 13 '18 at 19:06
  • Good to know @kumo your issue is resolved!! Also, you now have the power of "async/await" to use further.. – Nitish Narang Nov 13 '18 at 19:14