Improve getting result from onmessage event from WebSocket inside Promise

I have a WebSocket client that sends a message and receives a response afterwards. I want to be able to call it anywhere and store the response back, like this:

    // Global var
    var response

    // Connection to WS
    connect = () => {
        return new Promise(function(resolve, reject) {
            var server = new WebSocket('ws://localhost:8443/test/');
            server.onopen = function() {
                resolve(server);
            };
            server.onerror = function(err) {
                console.log(err)
                reject(err);
            };

        });
    }

    // Send request and receive message back
    async request(data){
        var request = {
            id: "test",
            data: data
        }

        connect().then((server) => {          

            server.send(JSON.stringify(request))

            server.onmessage = ({data}) => {
                response = JSON.parse(data) // save result
            }

        })
        .catch(function(err) {
            console.log(err)
        });
    }

    // Make request
    await request("test")
    
    // Access response
    console.log(response) // prints undefined

In the end response is still undefined because when it gets to console.log(response) the hasn’t been updated yet from the asynchronous call.

The only solution that I could come up to was to add a delay like this:

    delay(){
        var promise = new Promise(function(resolve) {
            window.setTimeout(function() {
              resolve('done!');
            }, 10);
          });
          return promise;
    }

    // Make request
    await request("test")

    // Await for delay 
    await delay();
    
    // Access response
    console.log(response) // prints data received!

Is this the right approach? Seems weird that adding 10ms of delay is enough for it to update. Please let me know if you have a better solution for this than the delay I added.

Answer

Your connect() call is missing await in front of it. On top of that, you never tell your promise to wait for the response itself. Here’s a quick-fix.

await connect().then((server) => {          

  server.send(JSON.stringify(request))

  return new Promise((resolve, reject) => {
    server.onmessage = ({data}) => {
      response = JSON.parse(data) // save result
      resolve()
    }
  })
})

Technically speaking, you could swap await for return since that’s your ending statement, and you’ve placed await on your request call.

return connect().then((server) => {          

  server.send(JSON.stringify(request))

  return new Promise((resolve, reject) => {
    server.onmessage = ({data}) => {
      response = JSON.parse(data) // save result
      resolve()
    }
  })
})

Although it works, I would encourage you to refactor connection init process – there’s absolutely no reason to reconnect to WS on every request attempt, unless you’re actually disconnecting from it on it’s completion.

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await