Race conditions in Javascript

Given all hype about javascript recently, it can come as a shock to realise that modern browsers still execute javascript on a single thread. For developers used to multi-threaded runtimes such as .NET and the JVM this can lead to unfamiliar results.

For this example we will consider the HTML 5 Websockets API.

Note first of all that there is no connect() method with websockets, the constructor itself initiates the connection.

Let us consider the following code. By adding a big loop we’ve essentially added a time delay between creating the websocket and wiring up the onopen event handler. Now the million dollar question: does the event handler always get triggered when a connection is made, or does the delay mean that the onopen handler is sometimes not wired up by the time the websocket has connected?

var wsUri, testWebSocket;
wsUri = "ws://localhost:8080/mywebsocket";
testWebSocket = function(){
  var connection, i$, i;
  connection = new WebSocket(wsUri);
  for (i$ = 0; i$ <= 1000000000; ++i$) {
    i = i$;
    /* simulate a delay */
  }
  console.log("wiring up event handler");
  connection.onopen = function(){
    console.log("opened");
  };
};
testWebSocket();

In the JVM/.NET world we would expect this to cause a race condition. If the websocket connects before our event handler has been wired up the handler would never get triggered. In fact though, this works fine in javascript and there is no race condition. Since the execution model is single-threaded the websocket doesn’t actually attempt to connect until nothing else is executing. Very much like a Dispatcher in WPF/Silverlight.

Of course you do still have to be very careful when using timers… this for example does indeed cause a race condition:

var wsUri, testWebSocket;
wsUri = "ws://localhost:8080/mywebsocket";
testWebSocket = function(){
  var connection, wireUp;
  connection = new WebSocket(wsUri);
  wireUp = function(){
    console.log("wiring up event handler");
    connection.onopen = function(){
      console.log("opened");
    };
  };
  window.setTimeout(wireUp, 5000);
};
testWebSocket();

Remember folks: Concurrency and asynchrony are not the same thing!

Personally I think this API design is a bit short-sighted… it’s going to make it very difficult to add real concurrency to javascript runtimes and browsers in the future without revamping the APIs and breaking existing apps.

See also:
http://ejohn.org/blog/how-javascript-timers-work/