Image by Andrew Martin from Pixabay

Yes, there are race conditions in JavaScript

Yuval Greenfield

--

Someone used this blog post as a reference for their claim that JavaScript does not have race conditions. That someone was as wrong as a thong on a bong.

To quote Wikipedia:

A race condition or race hazard is the behavior of an electronics, software, or other system where the system’s substantive behavior is dependent on the sequence or timing of other uncontrollable events.

Race conditions do not require parallelism. They just require a system to be sensitive to the sequence of operations or their timing. To prove that there are race conditions in vanilla JavaScript, here‘s a snippet:

// An example race condition in JavaScript
// When you run this script using Node or in a browser, it
// does not print "Ended with 0", but a random number.
// Even though the functions running
// simply loop 100 iterations of adding and subtracting.
// The reason the end result is random is because the
// sleeps are of random duration and the time between the read
// of the variable causes the eventual write to be incorrect
// when `adder` and `subber` interleave.
// This problem is similar to:
// https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use
let number = 0;
const times = 100;
function sleep() {
return new Promise((resolve) => setTimeout(resolve, Math.random() * 5));
}
async function adder() {
for (let i = 0; i < times; i++) {
await sleep();
let read = number;
read = read + 1;
await sleep();
number = read;
}
}
async function subber() {
for (let i = 0; i < times; i++) {
await sleep();
let read = number;
read = read - 1;
await sleep();
number = read;
}
}
async function main() {
console.log("Started with", number);
await Promise.all([
adder(),
subber(),
]);
console.log("Ended with", number);
}
main()
.then(() => console.log("All done"))
.catch((err) => console.error(err));

You can run that snippet in the browser or in Node and get random numbers out of it, even though it only adds a one 100 times to number and subtracts a one 100 times from number, the read-vs-write race condition causes the number to not zero out. Here are my results when running the above script multiple times using Node v12.4.0 on Windows 10:

D:\race-condition>node race-condition.js
Started with 0
Ended with -18
All done
D:\race-condition>node race-condition.js
Started with 0
Ended with 2
All done
D:\race-condition>node race-condition.js
Started with 0
Ended with 2
All done
D:\race-condition>node race-condition.js
Started with 0
Ended with -9
All done
D:\race-condition>node race-condition.js
Started with 0
Ended with -16
All done

Honestly I’m not sure why I’m even bothering because the blogger in their summary practically concedes that JavaScript can have race conditions and calls it “bad practice” and “silliness”. Well, duh. No one wants to write code with race conditions.

The most extreme definition

Even if I cover my eyes with the mental gymnastics blindfold, imagining that a JavaScript process cannot have race conditions — I can still imagine multiple processes running JavaScript creating a parallelism race condition. No matter how you spin it — developers working with JavaScript do have to deal with race conditions. There’s no practical ground upon which to claim race conditions in JavaScript don’t exist.

JS races happen, they are a huge pain to reproduce, debug, fix, and nobody likes them. Claiming there are no race conditions in JavaScript is like Kanye calling slavery a choice. It’s unempathetic, and just plain wrong. Don’t be like Kanye.

Do check out yosefk’s blog for more info on the difference between concurrency and parallelism.

--

--