75

When my page loads, I try to send a message to the server to initiate a connection, but it's not working. This script block is near the top of my file:

var connection = new WrapperWS();
connection.ident();
// var autoIdent = window.addEventListener('load', connection.ident(), false);

Most of the time, I see the error in the title:

Uncaught InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state

So I tried to catch the exception, as you can see below, but now it seems InvalidStateError is not defined and that produces a ReferenceError.

Here's the wrapper object for my websocket connection:

// Define WrapperWS

function WrapperWS() {
    if ("WebSocket" in window) {
        var ws = new WebSocket("ws://server:8000/");
        var self = this;

        ws.onopen = function () {
            console.log("Opening a connection...");
            window.identified = false;
        };
        ws.onclose = function (evt) {
            console.log("I'm sorry. Bye!");
        };
        ws.onmessage = function (evt) {
            // handle messages here
        };
        ws.onerror = function (evt) {
            console.log("ERR: " + evt.data);
        };

        this.write = function () {
            if (!window.identified) {
                connection.ident();
                console.debug("Wasn't identified earlier. It is now.");
            }
            ws.send(theText.value);
        };

        this.ident = function () {
            var session = "Test";
            try {
                ws.send(session);
            } catch (error) {
                if (error instanceof InvalidStateError) {
                    // possibly still 'CONNECTING'
                    if (ws.readyState !== 1) {
                        var waitSend = setInterval(ws.send(session), 1000);
                    }
                }
            }
        window.identified = true;
            theText.value = "Hello!";
            say.click();
            theText.disabled = false;
        };

    };

}

I am testing using Chromium on Ubuntu.

Fatih Acet
  • 28,690
  • 9
  • 51
  • 58
icedwater
  • 4,701
  • 3
  • 35
  • 50
  • Re "InvalidStateError is not defined and that produces a ReferenceError", I believe you can check`if (error.name === "InvalidStateError") {` – Collin Anderson Dec 20 '21 at 17:08

5 Answers5

56

You could send messages via a proxy function that waits for the readyState to be 1.

this.send = function (message, callback) {
    this.waitForConnection(function () {
        ws.send(message);
        if (typeof callback !== 'undefined') {
          callback();
        }
    }, 1000);
};

this.waitForConnection = function (callback, interval) {
    if (ws.readyState === 1) {
        callback();
    } else {
        var that = this;
        // optional: implement backoff for interval here
        setTimeout(function () {
            that.waitForConnection(callback, interval);
        }, interval);
    }
};

Then use this.send in place of ws.send, and put the code that should be run afterwards in a callback:

this.ident = function () {
    var session = "Test";
    this.send(session, function () {
        window.identified = true;
        theText.value = "Hello!";
        say.click();
        theText.disabled = false;
    });
};

For something more streamlined you could look into promises.

MichielB
  • 4,181
  • 1
  • 30
  • 39
Gigablah
  • 1,361
  • 15
  • 9
  • 3
    I guess, you forgot to supply `interval` parameter in `that.waitForConnection(callback, interval)` with , when you call it inside `setTimeout`. – Serhii Holinei Mar 06 '15 at 12:41
28

This error is raised because you are sending your message before the WebSocket connection is established.

You can solve it by doing this simply:

conn.onopen = () => conn.send("Message");

This onopen function waits for your WebSocket connection to establish before sending your message.

Manish
  • 909
  • 6
  • 15
  • 3
    You may have to json.stringify the sent message, but this worked for me: ```chatSocket.onopen = () => chatSocket.send(JSON.stringify({'message': 'Welcome to the chat. Type below to begin a conversation...' }));``` – ViaTech Jun 11 '20 at 14:57
  • 12
    In my experience, this does not suffice. Sometimes, I still get the poster's original error message even with code like what you suggest. – hrabinowitz Dec 23 '20 at 17:16
  • this is for client or server side? – Shivam Sahil Nov 19 '21 at 04:53
5

if you use one websocket client object and connect from random app places then object can be in connecting mode (concurent access).

if you want to exchange through only one websoket then create class with promise and keep it in property

class Ws {
  get newClientPromise() {
    return new Promise((resolve, reject) => {
      let wsClient = new WebSocket("ws://demos.kaazing.com/echo");
      console.log(wsClient)
      wsClient.onopen = () => {
        console.log("connected");
        resolve(wsClient);
      };
      wsClient.onerror = error => reject(error);
    })
  }
  get clientPromise() {
    if (!this.promise) {
      this.promise = this.newClientPromise
    }
    return this.promise;
  }
}

create singleton

window.wsSingleton = new Ws()

use clientPromise property in any place of app

window.wsSingleton.clientPromise
  .then( wsClient =>{wsClient.send('data'); console.log('sended')})
  .catch( error => alert(error) )

http://jsfiddle.net/adqu7q58/11/

Ramil Gilfanov
  • 552
  • 1
  • 8
  • 12
  • it still shows closing state error in below line wsClient =>{wsClient.send('data'); console.log('sended')}) – Ujjual Nov 09 '21 at 12:55
2

Method 1: Check connection

You can resolve a promise when socket is connected:

async function send(data) {
    await checkConnection();
    ws.send(data);
}

Implementation

This trick is implemented using an array of resolvers.

let ws = new WebSocket(url);

let connection_resolvers = [];
let checkConnection = () => {
    return new Promise((resolve, reject) => {
        if (ws.readyState === WebSocket.OPEN) {
            resolve();
        }
        else {
            connection_resolvers.push({resolve, reject});
        }
    });
}

ws.addEventListener('open', () => {
    connection_resolvers.forEach(r => r.resolve())
});

Method 2: Wait for connection

You can resolve a promise when socket is not connected:

const MAX_RETRIES = 4;
async function send(data, retries = 0) {
    try {
        ws.send(data);
    } 
    catch (error) {
        if (retries < MAX_RETRIES error.name === "InvalidStateError") {
            await waitForConnection();
            send(data, retries + 1);
        }
        else {
            throw error;
        }
    }
}

Implementation

This trick is implemented using an array of resolvers.

let ws = new WebSocket(url);

let connection_resolvers = [];
let waitForConnection = () => {
    return new Promise((resolve, reject) => {
        connection_resolvers.push({resolve, reject});
    });
}

ws.addEventListener('open', () => {
    connection_resolvers.forEach(r => r.resolve())
});

My opinion is that the second method has a little bit good performance!

Amir Fo
  • 5,163
  • 1
  • 43
  • 51
1

It is possible to use functions and readyState with setTimeout.

function openSocket()
{
    webSocket = new WebSocket("");
}
function sendData()
{
    if(webSocket.readyState)
    {
        webSocket.send(JSON.stringify(
        {
            "event"     : "", 
            "message"   : ""
        }));
    }
    else
    {
        setTimeout(sendData, 1000);
    }
}
function eventHandler()
{
    webSocket.onmessage = function(e)
    {
        data  = JSON.parse(e.data);
        event = data.event;

        switch (event)
        {...}
    }
}

openSocket();
sendData();
eventHandler();
icedwater
  • 4,701
  • 3
  • 35
  • 50
Rony Macfly
  • 210
  • 2
  • 4
  • 10