Javascript/Node/Express: res.json needs to wait for a function to finish running before returning... but res.json is impatient?

Viewed 1525

Hello friends! I hope you are well.

So in this snippet of code, I have a list of ten servers I want to query. These ten servers are represented by the ten ports I have defined up top in the const "ports".

The idea is to use forEach() on "ports", and run the query on each server. An object is returned by each run of the query and added into the initially empty array "data".

THEN! After "data" has been loaded with the ten objects containing the status of my servers, I want it dished back to the client side!

However..... this is not what happens. Port.forEach() runs the query just fine, and adds into the array "data"... but not before res.json jumps the gun and sends off an empty data back to my soon to be disappointed client.

So far... I've tried callbacks and async/await... but I haven't figured out the syntax for it.. Any tips would be awesome! Thank you for your time friendos!

module.exports = app => {
  app.get("/api/seMapServerStatus", (req, res) => {

  const ports = [ "27111", "27112", "27117", "27118", "27119", "27110", "27115", "27116", "27113", "27114" ]
  const data = []

    function hitServers(port){
        Gamedig.query({
          type: "aGameType",
          host: "theServer'sIP",
          port: port
        }).then((state) => {
          data.push(state)
          console.log("this is the server", state)
        }).catch((error) => {
          console.log("Server is offline");
        });  
    };

    ports.forEach(port => {
      hitServers(port)
    })
  });
  console.log("and here is the final server list", data)
  res.json(data);   
}

1 Answers

The above code executes synchronously, therefore you return in the same frame before any promise has the chance to resolve.

We can clean the above code up as follows:

module.exports = app => {

  app.get("/api/seMapServerStatus", (req, res) => {
    const ports = ["27111", "27112", "27117", "27118", "27119", "27110", "27115", "27116", "27113", "27114"]

    function hitServers(port) {
      return Gamedig.query({
        type: "aGameType",
        host: "theServer'sIP",
        port: port
      })
    }

    // With error handling
    function hitServersSafe(port) {
      return hitServers(port)
        .then(result => {
          return {
            success: true,
            result: result
          }
        })
        .catch(error => {
          return {
            success: false,
             // you probably need to serialize error
            error: error
          }
        })
    }

    const promises = ports.map(port => hitServers(port))
    // With error handling
    // const promises = ports.map(port => hitServersSafe(port))

    Promise
      .all(promises)
      .then(data => res.json(data))
      .catch(error => {
        // do something with error
      })
  })

}

We map each port to a promise. After we have a list of X promises we wait for all of them to finish.

The calling Promise.all() returns an array of resolved values, or rejects when any promise rejects.

Only after all of the promises have resolved we can move on and send the result to the client.

Related