loading...

Wait for the WebSocket connection to be open, before sending a message

ndrbrt profile image Andrea Bertoloni ・1 min read

Sometimes when you open a new WebSocket connection and try to send a message, you can receive an Error reporting something like this: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state., because the connection is not open yet.

To avoid that, I saw some people suggesting to send the message on the onopen event; something like:

socket.onopen = () => socket.send(message)

This approach, though, only works if you need to trigger send() once (on open).

A more flexible approach would be to wait for the connection to be open (if it's not already).

The waitForOpenConnection function, in the example below, returns a Promise that resolves when the connection is finally open, checking it every n milliseconds.
If, after a given number of checks, the connection is still not open, the Promise is rejected.
In the sendMessage function, you can see an example of the usage.

const waitForOpenConnection = (socket) => {
    return new Promise((resolve, reject) => {
        const maxNumberOfAttempts = 10
        const intervalTime = 200 //ms

        let currentAttempt = 0
        const interval = setInterval(() => {
            if (currentAttempt > maxNumberOfAttempts - 1) {
                clearInterval(interval)
                reject(new Error('Maximum number of attempts exceeded'))
            } else if (socket.readyState === socket.OPEN) {
                clearInterval(interval)
                resolve()
            }
            currentAttempt++
        }, intervalTime)
    })
}

const sendMessage = async (socket, msg) => {
    if (socket.readyState !== socket.OPEN) {
        try {
            await waitForOpenConnection(socket)
            socket.send(msg)
        } catch (err) { console.error(err) }
    } else {
        socket.send(msg)
    }
}

Link to the GitHub gist

Discussion

markdown guide
 

Nice way of solving the race condition in your code! 👍

With reuse of the socket.send() method being a critical aspect of websocket functionality, it makes sense that you wouldn't want to have it be called in socket.onopen just the one time. Looking at how your code is structured, I wonder if you could avoid (or simplify) the timeout logic by resolving the promise once a true value is seen on checking for the socket to be open?

function waitForOpenSocket(socket) {
  return new Promise((resolve, _reject) => {
    while (socket.readyState !== socket.OPEN) { /* no-op */ }
    return resolve()
  })
}

async function sendMessage(socket, msg) {
  await waitForOpenSocket(socket)
  socket.send(msg)
}

I came into Javascript after async/await had already been standardized, and haven't worked much with the Promise API directly, so I don't actually know whether my while loop gives program execution back to the rest of the app, or makes the app hang until the socket is open... but it should let the app continue working, if I understand promises as much as I think I do.

 

Well, my point is: since the open state depends on a network request that takes a certain amount of time to be completed and could eventually fail, I want my code to check the status only at given intervals; furthermore, since the request could fail, I don't to want to end up stuck in that loop forever, but eventually break it after n checks.

You could use async/await, but sometimes I just feel more comfortable using a Promise instead (after all an async function is just a sort of a shortcut that returns a Promise)