【问题标题】:How to wait for a WebSocket's readyState to change如何等待 WebSocket 的 readyState 改变
【发布时间】:2012-11-12 20:12:50
【问题描述】:

我正在尝试实现一个带有轮询后备的 WebSocket。如果 WebSocket 连接成功,readyState 变为 1,但如果失败,readyState 为 3,我应该开始轮询。

我尝试过这样的事情:

var socket = new WebSocket(url);
socket.onmessage = onmsg;
while (socket.readyState == 0)
{
}
if (socket.readyState != 1)
{
    // fall back to polling
    setInterval(poll, interval);
}

我期待socket.readyState 异步更新,并允许我立即阅读。但是,当我运行它时,我的浏览器冻结了(我在放弃之前将其打开了大约半分钟)。

我以为可能有 onreadyStateChanged 事件,但我在 MDN 参考资料中没有看到。

我应该如何实施?显然,一个空循环是行不通的,并且没有任何事件。

【问题讨论】:

  • 呃...我可能遗漏了一些东西,但你为什么不使用socket.onopen
  • @raina77ow 如果套接字从未打开,我认为socket.onopen 不会触发。
  • 是的,但是在这种情况下您的代码应该做什么?我想,“等待套接字打开”显然不是答案。 )
  • @raina77ow 如果套接字无法打开,它应该回退到轮询。
  • 我还是有点失落,抱歉,但是为此使用onerror 事件处理程序有什么问题?

标签: javascript websocket polling


【解决方案1】:

您的 while 循环可能会锁定您的线程。尝试使用:

setTimeout(function(){
    if(socket.readyState === 0) {
        //do nothing
    } else if (socket.readyState !=1) {
        //fallback
        setInterval(poll, interval);
    }
}, 50);

【讨论】:

  • 第一个 if 究竟是为了什么?
  • 如果我理解正确,readyState 不会立即更新。这就是我使用循环的原因。
  • 老实说我不知道​​,我假设与 OP 问题中的 while 循环相同。它当然可以被删除,除非 OP 需要在socket.readyState === 0 时完成某些事情,我尽量不留下任何假设。
  • readyState 为零时,我确实需要做一些事情,那就是等待。
  • 我会尝试一个 if 循环来检查套接字更改@KendallFrey,使用短暂的 setTimeout 再次检查它是否仍然为 0,如果它已更改则退出。不理想,while 应该可以工作,但没有它会使诊断更容易。
【解决方案2】:

就像定义onmessage 处理程序一样,您也可以定义onerror 处理程序。连接失败时会调用这个。

var socket = new WebSocket(url);
socket.onmessage = onmsg;
socket.onerror = function(error) {
    // connection failed - try polling
}

【讨论】:

  • 我测试过,它没有触发。
  • 我在我的应用程序中使用它,只要与服务器的连接出现问题,它就被称为非常可靠。你的代码中还有那种忙碌的情况吗?这可能会使 JS 引擎太忙而无法执行错误处理。
  • 我注释掉了while循环,并用onerror做了一个快速测试。它没有运行。我很确定服务器也不会立即关闭,因为onopen 也没有运行。我认为服务器拒绝连接,而不是发生错误。
  • 快进到 2016... 它不会在 Safari (9.1.2) 上触发,但在 Chrome 和 Firefox 上会触发。
【解决方案3】:

这里有一个更详细的解释。首先,检查特定的浏览器 API,因为并非所有浏览器都使用最新的 RFC。您可以咨询

您不想运行循环来不断检查就绪状态,这是您不需要的额外开销。更好的方法是了解与就绪状态更改相关的所有事件,然后将它们适当地连接起来。它们如下:

onclose 当 WebSocket 连接的 readyState 变为 CLOSED 时调用的事件监听器。侦听器收到一个名为“close”的 CloseEvent。

onerror 发生错误时调用的事件监听器。这是一个名为“error”的简单事件。

onmessage 从服务器接收到消息时要调用的事件侦听器。侦听器收到一个名为“消息”的 MessageEvent。

onopen WebSocket 连接的 readyState 变为 OPEN 时调用的事件监听器;这表明连接已准备好发送和接收数据。该事件是一个名称为“open”的简单事件。

JS 完全是事件驱动的,因此您只需连接所有这些事件并检查就绪状态,这样您就可以相应地从 WS 切换到轮询。

我建议您查看 Mozilla 参考,它比 RFC 文档更易于阅读,并且可以很好地概述 API 及其工作原理 (link)。

如果失败,请不要忘记进行回调以进行重试并进行轮询,直到触发成功重新连接的回调。

【讨论】:

  • "您可以咨询"...? (我知道它很旧)
  • 读到这里,很有意义,除了所有on 事件不公开关闭和连接。只有 onopen、onclose 和 error 事件。我不想在关闭状态下向 ws 发送消息。
【解决方案4】:

关注http://dev.w3.org/html5/websockets/

搜索“事件处理程序”并找到表。

打开 -> 打开
onmessage -> 消息
错误->错误
onclose ->关闭

function update(e){ /*Do Something*/};
var ws = new WebSocket("ws://localhost:9999/");

ws.onmessage = update;

【讨论】:

  • 如果 w3 为 readyState 提供一个真实的状态机图就好了。例如。当连接被目的地拒绝时,它也可以从 CONNECTING 变为 CLOSED。
【解决方案5】:

这很简单,而且效果很好...您可以添加关于最大时间的条件,或尝试次数以使其更健壮...

function sendMessage(msg){
    // Wait until the state of the socket is not ready and send the message when it is...
    waitForSocketConnection(ws, function(){
        console.log("message sent!!!");
        ws.send(msg);
    });
}

// Make the function wait until the connection is made...
function waitForSocketConnection(socket, callback){
    setTimeout(
        function () {
            if (socket.readyState === 1) {
                console.log("Connection is made")
                if (callback != null){
                    callback();
                }
            } else {
                console.log("wait for connection...")
                waitForSocketConnection(socket, callback);
            }

        }, 5); // wait 5 milisecond for the connection...
}

【讨论】:

  • 可能只是我遗漏了一些东西,但是当我运行您的代码时,我收到一个错误消息,告诉我“回调不是函数”。为什么回调函数在if(callback != null){ callback(); }
  • 根据Axel's answer,适当的解决方案是使用事件处理程序。
  • 我觉得奇怪的是 websockets 是一种推送技术,但 readyState 却是一种同步的非推送调用。
【解决方案6】:

在我的用例中,如果连接失败,我想在屏幕上显示错误。

let $connectionError = document.getElementById("connection-error");

setTimeout( () => {
  if (ws.readyState !== 1) {
    $connectionError.classList.add( "show" );
  }
}, 100 );  // ms

请注意,在 Safari (9.1.2) 中,不会触发 error 事件 - 否则我会将其放在错误处理程序中。

【讨论】:

    【解决方案7】:

    我根本没有使用池。相反,我使用排队。 首先我创建一个新的发送函数和一个队列:

    var msgs = []
    function send (msg) {
      if (ws.readyState !== 1) {
        msgs.push(msg)
      } else {
        ws.send(msg)
      }
    }
    

    那么我需要在第一次建立连接时读取和发送:

    function my_element_click () {
      if (ws == null){
        ws = new WebSocket(websocket_url)
        ws.onopen = function () {
          while (msgs.length > 0) {
            ws.send(msgs.pop())
          }
        }
        ws.onerror = function(error) {
          // do sth on error
        }
      } 
      msg = {type: 'mymessage', data: my_element.value}
      send(JSON.stringify(msg))
    }
    

    此示例中的 WebSocket 连接仅在第一次单击时创建。通常,在第二个消息开始直接发送。

    【讨论】:

      【解决方案8】:

      如果您使用async/await 并且只想等到连接可用,我建议您使用此功能:

      async connection (socket, timeout = 10000) {
        const isOpened = () => (socket.readyState === WebSocket.OPEN)
      
        if (socket.readyState !== WebSocket.CONNECTING) {
          return isOpened()
        }
        else {
          const intrasleep = 100
          const ttl = timeout / intrasleep // time to loop
          let loop = 0
          while (socket.readyState === WebSocket.CONNECTING && loop < ttl) {
            await new Promise(resolve => setTimeout(resolve, intrasleep))
            loop++
          }
          return isOpened()
        }
      }
      

      用法(在async函数中):

      const websocket = new WebSocket('...')
      const opened = await connection(websocket)
      if (opened) {
        websocket.send('hello')
      }
      else {
        console.log("the socket is closed OR couldn't have the socket in time, program crashed");
        return
      }
      

      【讨论】:

        【解决方案9】:

        tl;dr

        一个简单的proxy 包装器,用于将state 事件添加到WebSocket,当其readyState 更改时将发出该事件:

        const WebSocketProxy = new Proxy(WebSocket, {
            construct: function(target, args) {
                // create WebSocket instance
                const instance = new target(...args);
        
                //internal function to dispatch 'state' event when readyState changed
                function _dispatchStateChangedEvent() {
                    instance.dispatchEvent(new Event('state'));
                    if (instance.onstate && typeof instance.onstate === 'function') {
                        instance.onstate();
                    }
                }
        
                //dispatch event immediately after websocket was initiated
                //obviously it will be CONNECTING event
                setTimeout(function () {
                    _dispatchStateChangedEvent();
                }, 0);
        
                // WebSocket "onopen" handler
                const openHandler = () => {
                    _dispatchStateChangedEvent();
                };
        
                // WebSocket "onclose" handler
                const closeHandler = () => {
                    _dispatchStateChangedEvent();
                    instance.removeEventListener('open', openHandler);
                    instance.removeEventListener('close', closeHandler);
                };
        
                // add event listeners
                instance.addEventListener('open', openHandler);
                instance.addEventListener('close', closeHandler);
        
                return instance;
            }
        });
        

        长解释

        您可以使用Proxy 对象来监控内部WebSocket 状态。

        这是一篇很好的文章,它解释了如何做到这一点Debugging WebSockets using JS Proxy Object

        下面是上面文章中的代码 sn-p 示例,以防该站点将来无法使用:

        // proxy the window.WebSocket object
        var WebSocketProxy = new Proxy(window.WebSocket, {  
          construct: function(target, args) {
            // create WebSocket instance
            const instance = new target(...args);
        
            // WebSocket "onopen" handler
            const openHandler = (event) => {
              console.log('Open', event);
            };
        
            // WebSocket "onmessage" handler
            const messageHandler = (event) => {
              console.log('Message', event);
            };
        
            // WebSocket "onclose" handler
            const closeHandler = (event) => {
              console.log('Close', event);
              // remove event listeners
              instance.removeEventListener('open', openHandler);
              instance.removeEventListener('message', messageHandler);
              instance.removeEventListener('close', closeHandler);
            };  
        
            // add event listeners
            instance.addEventListener('open', openHandler);
            instance.addEventListener('message', messageHandler);
            instance.addEventListener('close', closeHandler);
        
            // proxy the WebSocket.send() function
            const sendProxy = new Proxy(instance.send, {
              apply: function(target, thisArg, args) {
                console.log('Send', args);
                target.apply(thisArg, args);
              }
            });
        
            // replace the native send function with the proxy
            instance.send = sendProxy;
        
            // return the WebSocket instance
            return instance;
          }
        });
        
        // replace the native WebSocket with the proxy
        window.WebSocket = WebSocketProxy; 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-07
          • 2014-12-28
          • 1970-01-01
          • 2023-03-29
          相关资源
          最近更新 更多