开发者

Do I need to be concerned with race conditions with asynchronous Javascript?

Suppose I load some Flash movie that I know at some point in the future will call window.flashReady and will set window.flashReadyTriggered = true.

Now I have a block of code that I want to have executed when the Flash is ready. I want it to execute it immediately if window.flashReady has already been called and I want to put it as the callback in window.flashReady if it has not yet been called. The naive approach is this:

if(window.flashReadyTriggered) {
  block();
} else {
  window.flashReady = block;
}

So the concern I have based on this is that the expression in the if condition is evaluated to false, but then before block() can be executed, window.flashReady is triggered by the external Flash. Consequently, block is never called.

Is there a better design pattern to accomplish the higher level goal I'm going for (e.g., manually calling the flashReady callback)? If not, am I safe, or are the开发者_开发百科re other things I should do?


All Javascript event handler scripts are handled from one master event queue system. This means that event handlers run one at a time and one runs until completion before the next one that's ready to go starts running. As such, there are none of the typical race conditions in Javascript that one would see in a multithreaded language where multiple threads of the language can be running at once (or time sliced) and create real-time conflict for access to variables.

Any individual thread of execution in javascript will run to completion before the next one starts. That's how Javascript works. An event is pulled from the event queue and then code starts running to handle that event. That code runs by itself until it returns control to the system where the system will then pull the next event from the event queue and run that code until it returns control back to the system.

Thus the typical race conditions that are caused by two threads of execution going at the same time do not happen in Javascript.

This includes all forms of Javascript events including: user events (mouse, keys, etc..), timer events, network events (ajax callbacks), etc...

The only place you can actually do multi-threading in Javascript is with the HTML5 Web Workers or Worker Threads (in node.js), but they are very isolated from regular javascript (they can only communicate with regular javascript via message passing) and cannot manipulate the DOM at all and must have their own scripts and namespace, etc...


While I would not technically call this a race condition, there are situations in Javascript because of some of its asynchronous operations where you may have two or more asynchronous operations in flight at the same time (not actually executing Javascript, but the underlying asynchronous operation is running native code at the same time) and it may be unpredictable when each operation will complete relative to the others. This creates an uncertainty of timing which (if the relative timing of the operations is important to your code) creates something you have to manually code for. You may need to sequence the operations so one runs and you literally wait for it to complete before starting the next one. Or, you may start all three operations and then have some code that collects all three results and when they are all ready, then your code proceeds.

In modern Javascript, promises are generally used to manage these types of asynchronous operations.

So, if you had three asynchronous operations that each return a promise (like reading from a database, fetching a request from another server, etc...), you could manually sequence then like this:

a().then(b).then(c).then(result => {
    // result here
}).catch(err => {
    // error here
});

Or, if you wanted them all to run together (all in flight at the same time) and just know when they were all done, you could do:

Promise.all([a(), b(), c()])..then(results => {
    // results here
}).catch(err => {
    // error here
});

While I would not call these race conditions, they are in the same general family of designing your code to control indeterminate sequencing.


There is one special case that can occur in some situations in the browser. It's not really a race condition, but if you're using lots of global variables with temporary state, it could be something to be aware of. When your own code causes another event to occur, the browser will sometimes call that event handler synchronously rather than waiting until the current thread of execution is done. An example of this is:

  1. click
  2. the click event handler changes focus to another field
  3. that other field has an event handler for onfocus
  4. browser calls the onfocus event handler immediately
  5. onfocus event handler runs
  6. the rest of the click event handler runs (after the .focus() call)

This isn't technically a race condition because it's 100% known when the onfocus event handler will execute (during the .focus() call). But, it can create a situation where one event handler runs while another is in the middle of execution.


JavaScript is single threaded. There are no race conditions.

When there is no more code to execute at your current "instruction pointer", the "thread" "passes the baton", and a queued window.setTimeout or event handler may execute its code.

You will get better understanding for Javascript's single-threading approach reading node.js's design ideas.

Further reading: Why doesn't JavaScript support multithreading?


It is important to note that you may still experience race conditions if you eg. use multiple async XMLHttpRequest. Where the order of returned responses is not defined (that is responses may not come back in the same order they were send). Here the output depends on the sequence or timing of other uncontrollable events (server latency etc.). This is a race condition in a nutshell.

So even using a single event queue (like in JavaScript) does not prevent events coming in uncontrollable order and your code should take care of this.


Sure you need. It happens all the time:

<button onClick=function() {
  const el = document.getElementById("view");
  fetch('/some/api').then((data) => {
    el.innerHTML = JSON.stringify(data);
  })
}>Button 1</button>

<button onClick=function() {
  const el = document.getElementById("view");
  fetch('/some/other/api').then((data) => {
    el.innerHTML = JSON.stringify(data);
  })

}>Button 2</button>

Some people don't view it as a race condition.

But it really is.

Race condition is broadly defined as "the behavior of an electronic, software, or other system where the output is dependent on the sequence or timing of other uncontrollable events".

If user clicks these 2 buttons in a brief period, the output is not guaranteed to depend of the order of clicking. It depends on which api request will be resolved sooner. Moreover, the DOM element you're referencing can be removed by some other event (like changing route).

You can mitigate this race condition by disabling button or showing some spinner when loading operation in progress, but that's cheating. You should have some mutex/counter/semaphore at the code level to control your asynchronous flow.

To adapt it to your question, it depends on what "block()" is. If it's a synchronous function, you don't need to worry. But if it's asynchronous, you have to worry:

  function block() {
    window.blockInProgress = true;
    // some asynchronous code
    return new Promise(/* window.blockInProgress = false */);
  }

  if(!window.blockInProgress) {
    block();
  } else {
    window.flashReady = block;
  }

This code makes sense you want to prevent block from being called multiple times. But if you don't care, or the "block" is synchronous, you shouldn't worry. If you're worried about that a global variable value can change when you're checking it, you shouldn't be worried, it's guaranteed to not change unless you call some asynchronous function.

A more practical example. Consider we want to cache AJAX requests.

 fetchCached(params) {
   if(!dataInCache()) {
     return fetch(params).then(data => putToCache(data));
   } else {
     return getFromCache();
   }
 }

So happens if we call this code multiple times? We don't know which data will return first, so we don't know which data will be cached. The first 2 times it will return fresh data, but the 3rd time we don't know the shape of response to be returned.


Yes, of course there are race conditions in Javascript. It is based on the event loop model and hence exhibits race conditions for async computations. The following program will either log 10 or 16 depending on whether incHead or sqrHead is completed first:

const rand = () => Math.round(Math.random() * 100);

const incHead = xs => new Promise((res, rej) =>
  setTimeout(ys => {
    ys[0] = ys[0] + 1;
    res(ys);
  }, rand(), xs));

const sqrHead = xs => new Promise((res, rej) =>
  setTimeout(ys => {
    ys[0] = ys[0] * ys[0];
    res(ys);
  }, rand(), xs))

const state = [3];

const foo = incHead(state);

const bar = sqrHead(state);

Promise.all([foo, bar])
  .then(_ => console.log(state));

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜