Skip to content

Instantly share code, notes, and snippets.

@ivenmarquardt
Last active December 22, 2019 14:34
Show Gist options
  • Save ivenmarquardt/21271e91000a56c3522ad62eeb00114b to your computer and use it in GitHub Desktop.
Save ivenmarquardt/21271e91000a56c3522ad62eeb00114b to your computer and use it in GitHub Desktop.

Asynchronous operations entail race conditions provided they are evaluated in parellel. However, a counter should always be evaluated in sequence as per logic. I'd claim this reveals another flaw of Javascript's Promise type: It is actually a blend of the two types (sequential) Task and Parallel. While the former is a monad the latter is only an applicative.

Bergi has a point, of course. Asynchronous operations in parallel should solely rely on immutable data. However, operations that are inherently sequential should be evaluated as such and thus it doesn't matter if the underlying data is immutable or not.

I am not that versed in dealing with promises, so I probably doesn't use the type correctly. However, it should be enough to illustrate my point.

    let clock = 0;

    const incSlowly = o => new Promise(res => {
      if (clock == 12)
        clock = 0; // reset
      
      delay(1000, x => (o.value = x, res(x)), clock++);
    });

    const delay = (t, f, x) => {
      return new Promise(res => setTimeout(y => res(f(y)), t, x));
    };
    <output id="out">0</output>
    <button onclick="incSlowly(document.getElementById('out'))">Tick!</button>
@adit-hotstar
Copy link

Consider Bergi's original example.

var clock = out.value = 0;

async function incrementSlowly() {
  if (clock == 12)
    clock = 0; // reset
  await delay(1000);
  clock++;
  out.value = clock;
}
function delay(t) { return new Promise(resolve => setTimeout(resolve, t)); }

The problem with this example is that the value of clock before and after the await line might not be the same. Hence, if the value of clock is currently 11 and you quickly call the incrementSlowly function multiple times then here's what'll happen.

Call # Value of clock just before await Value of clock just after await
1 11 11
2 11 12
3 11 13

Notice that only the first call has the same clock value before and after the await line. The second call has different clock values because the first call increments clock after it is resumed.

One way to solve this problem would be to move the clock++ line before the await line as follows.

var clock = out.value = 0;

async function incrementSlowly() {
  if (clock == 12)
    clock = 0; // reset
  clock++;
  await delay(1000);
  out.value = clock;
}
function delay(t) { return new Promise(resolve => setTimeout(resolve, t)); }

Now, if the value of clock is currently 11 and you quickly call the incrementSlowly function multiple times then here's what'll happen.

Call # Value of clock just before await Value of clock just after await
1 12 12
2 1 1
3 2 2

Since the value of clock is not updated after incrementSlowly is resumed, it will always remain consistent. This is what your example does too. It increments the value of clock before calling delay. You also introduce an intermediate variable x but that's not necessary.

Notice that this has nothing to do with Promise, Task, Parallel, Applicative, or Monad, and it has everything to do with concurrency. As Bergi mentions, you can replace the await with a setTimeout in the above examples and it would still work the same.

In your original question, you wrote the following.

JS models concurrency by an event loop. As a result there are no race conditions.

This is a false statement. If you have concurrency and mutability, you can have race conditions. An event loop doesn't magically prevent race conditions. Bergi's answer shows an example of a race condition in JavaScript, thereby disproving your assumption that, “there are no race conditions.”

Hence, to answer your question.

What is a typical class of issues that is caused by mutable data types in a single-threaded environment?

Race conditions.

@joakim
Copy link

joakim commented Dec 22, 2019

It's important to remember that promises and await is basically sugar on top of callbacks. It does wonders for readability and code structure, but it's still callback hell underneath.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment