Today I learned — episode 4 (numbers in JavaScript considered useless)

December 21, 2016

This blog series is supposed to cover short topics in software development, learnings from working in software companies, tooling, etc.

Numbers in JavaScript considered useless

For my hobby web application project, I wanted to implement a simple use case: my music player application needs to know the playback status including some other fields, and retrieves that status using AJAX calls to the local server. While that should be pretty fast in theory, every network request will slow down your (JavaScript) application, especially if we assume that the web server might not always be on localhost. An easy way to circumvent this are bidirectional WebSocket messages (here: server pushes status). However I’m playing with Rust and the nickel.rs web framework so I just wanted a quick solution without having to add WebSocket support.

My idea was to just have the server sleep during the request until the playback status has actually changed. This way, the client makes a request which simply takes longer if the status remains unchanged, resulting in fewer connections being made. I added a GET parameter previous_hash to the URL so the server could check if the status had changed from what the client stored earlier. Using Rust’s Hash trait, it was very simple to create a u64 hash of my struct and send the new hash back to the client.

In Rust pseudo-code:

router.get("/app/status", {
    middleware! { |request, mut response|
        let previous_hash : Option<u64> = request.query().get("previous_hash")
                                                         .map(|s| s.parse::<u64>().expect("previous_hash not an integer"));
        response.set(MediaType::Json);

        // Delay response for some time if nothing changes, to help client make fewer calls
        let mut ret = None;
        for i in 0..100
        {
            let status_response = get_status_response(); // unnecessary detail
            if previous_hash != Some(status_response.status_hash) {
                ret = Some(json::encode(&status_response).unwrap());
                break;
            }
            sleep_ms(50);
        }

        // If nothing changed while we slept ~100*50 milliseconds, just send latest status
        if ret.is_none() {
            let status_response = get_status_response();
            ret = Some(json::encode(&status_response).unwrap())
        }
        ret.unwrap()
}
});

A change so simple should have just worked, but even though the playback status of my music player remained the same, my requests kept taking 1 millisecond without any sleep calls. The web developer tools in Firefox quickly showed me the potential problem:

JSON response view
Raw response view

The JSON response view and the raw response from the server showed different values. OMFG this must be a browser bug showing big numbers — let’s file a bug on Firefox! Just joking, this was not the real problem, but my first suspect was Firefox simply because I’m using the nightly version.

Long story short: I wasted some nerves and time just to stumble over the same old JavaScript problem again. Numbers in JS are all IEEE 754 floating point. Firefox was showing me the correct thing. My Rust-based web server could easily output the exact u64 integer value while JavaScript converts to floating point, losing precision and making comparisons and any other use of (big) numbers for my hashing use case totally useless. That means I have to switch to using a string representation of the number instead.

While this is just another WAT moment, I am hoping that WebAssembly (supposed to include 64-bit types at some point) and languages that compile to that target can alleviate such problems for the sake of a better future of web development.