javascriptwebsocket

How can I guarantee that websocket onopen to be called


I use WebSocket in javascript. WebSocket requires the url as constructor parameter and immediately try to connect. I can set onopen method only after construct it.

So, if WebSocket already establishes the connection before I set onopen, then I miss onopen event!

How can I avoid this?

To simulate it:

A)

1) In Chrome, open websocket. 2) Press F12 to open Developer Tools. 3) Open Console 4) Copy and paste all of these codes at once! Enter!

uri = "ws://echo.websocket.org?encoding=text";
websocket = new WebSocket(uri);
websocket.onopen = function(evt) { console.log('EHE')};

B)

Repeat 1-2-3

4) Copy and paste these codes and run

uri = "ws://echo.websocket.org?encoding=text";
websocket = new WebSocket(uri);

5) Wait a second

6) Run this code:

websocket.onopen = function(evt) { console.log('EHE')};

Result:

In A) onopen is called. In B) we missed it!


Solution

  • Because of the single-threaded event driven nature of Javascript, what you describe will not happen in real code. The "open" event can't be triggered until after your current section of Javascript finishes. Thus, you will always be able to set your onopen event handler before the event occurs.

    Inserting artificial pauses in the debugger or in the console is an artificial situation that does not occur in real code.

    What happens in real code is this:

    1. You call new WebSocket(uri)
    2. The webSocket intrastructure initiates a webSocket connection (an asynchronous operation)
    3. The webSocket constructor returns immediately before that connection has completed.
    4. The rest of your code runs (including assigning the .onopen property and setting up your event handler.
    5. The Javascript interpreter is done running your code and returns back to the event loop.
    6. If, by now, the webSocket has connected, then there will be an open event in the event queue and Javascript will trigger that event, resulting in your .onopen handler getting called.
    7. If the open event has not yet completed, Javascript will wait for the next event to be inserted into the event queue and will run it, repeating that process over and over. Eventually one of these events will be your open event.

    The key to this is that .onopen is called via an asynchronous event. Thus, it has to go through the Javascript event queue. And, no events from the event queue can be run until after your current section of Javascript finishes and returns back to the interpreter. That's how the "event-driven" nature of Javascript works. So, because of that architecture, you cannot miss the onopen event as long as you install your .onopen handler in the same section of Javascript that calls the constructor.


    If it provides you any comfort, there are dozens of APIs in node.js that all rely on this same concept. For example when you create a file stream with fs.createReadStream(filename), you have to create the stream, then add event handlers (one of those event handlers is for an open event). The same logic applies there. Because of the event-driven nature of Javascript, there is no race condition. The open event or the error event cannot be triggered before the rest of your Javascript runs so you always have an opportunity to get your event handlers installed before they could get called.

    In cases where errors could be detected synchronously (like a bad filename or bad uri) and could trigger an error event immediately, then code using something like setImmediate(function() { /* send error event here*/ }) to make sure the error event is not triggered until after your code has a chance to install your event handlers.


    Update in 2024:

    Since Javascript has await, you also have to watch out for attaching an onopen event handler after an await. If the await is between the creation of the resource and where you start listening for some event on the resource, then that would be a situation where you could miss the event because if it happens while the interpreter is awaiting, it will occur before you have a listener for it and you will miss it. The original question here did not have that situation in the code (all code was synchronous) before the event handler was installed so it did not have this issue. But, with the wide use of await, you do have to be careful about this issue when using await. Remember that await pauses this particular function execution, but does not stop the interpreter from processing other events and running other code.