Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 360
icedwater Avatar asked Apr 14 '14 03:04

icedwater


4 Answers

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.

like image 169
Gigablah Avatar answered Oct 01 '22 15:10

Gigablah


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.

like image 41
Manish Avatar answered Oct 01 '22 13:10

Manish


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/

like image 5
Ramil Gilfanov Avatar answered Oct 01 '22 15:10

Ramil Gilfanov


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!

like image 2
Amir Fo Avatar answered Oct 01 '22 15:10

Amir Fo