我是否需要关注异步 Javascript 的竞争条件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7238586/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Do I need to be concerned with race conditions with asynchronous Javascript?
提问by Steven
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
.
假设我加载了一些 Flash 电影,我知道在未来的某个时候会调用window.flashReady
并设置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:
现在我有一段代码,希望在 Flash 准备好时执行。如果window.flashReady
已经被调用,我希望它立即执行它,window.flashReady
如果它还没有被调用,我想把它作为回调。天真的方法是这样的:
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.
所以我基于此担心的是,if
条件中的表达式被评估为false
,但在block()
可以执行之前,window.flashReady
是由外部 Flash 触发的。因此,block
永远不会被调用。
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 there other things I should do?
是否有更好的设计模式来实现我要实现的更高级别的目标(例如,手动调用flashReady
回调)?如果没有,我是否安全,或者还有其他我应该做的事情吗?
采纳答案by kay - SE is evil
JavaScript is single threaded. There are no race conditions.
JavaScript 是单线程的。没有竞争条件。
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.
当在您当前的“指令指针”处没有更多代码要执行时,“线程”“传递指挥棒”,并且排队window.setTimeout
或事件处理程序可能会执行其代码。
You will get better understanding for Javascript's single-threading approach reading node.js's design ideas.
阅读node.js的设计思路,您将对 Javascript 的单线程方法有更好的理解。
Further reading: Why doesn't JavaScript support multithreading?
进一步阅读: 为什么 JavaScript 不支持多线程?
回答by jfriend00
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.
所有 Javascript 事件处理程序脚本都由一个主事件队列系统处理。这意味着事件处理程序一次运行一个,一个运行直到完成,然后准备运行的下一个开始运行。因此,在 Javascript 中不存在人们在多线程语言中会看到的典型竞争条件,其中该语言的多个线程可以同时运行(或时间片)并为访问变量创建实时冲突。
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.
javascript 中的任何单个执行线程都将在下一个线程开始之前运行完成。这就是 Javascript 的工作原理。从事件队列中提取一个事件,然后代码开始运行以处理该事件。该代码自行运行,直到将控制权返回给系统,然后系统将从事件队列中提取下一个事件并运行该代码,直到将控制权返回给系统。
Thus the typical race conditions that are caused by two threads of execution going at the same time do not happen in Javascript.
因此,由两个同时执行的线程引起的典型竞争条件不会在 Javascript 中发生。
This includes all forms of Javascript events including: user events (mouse, keys, etc..), timer events, network events (ajax callbacks), etc...
这包括所有形式的 Javascript 事件,包括:用户事件(鼠标、按键等)、定时器事件、网络事件(ajax 回调)等...
The only place you can actually do multi-threading in Javascript is with the HTML5 Web Workersor 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...
唯一可以在 Javascript 中实际执行多线程的地方是HTML5 Web Workers或Worker Threads(在 node.js 中),但它们与常规 javascript 非常隔离(它们只能通过消息传递与常规 javascript 通信)并且不能完全操作 DOM 并且必须有自己的脚本和命名空间等...
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.
虽然我不会在技术上将其称为竞争条件,但在 Javascript 中存在一些情况,因为它的某些异步操作可能同时进行两个或多个异步操作(实际上不是执行 Javascript,但底层异步操作是同时运行本机代码),并且每个操作相对于其他操作何时完成可能是不可预测的。这会造成时间的不确定性(如果操作的相对时间对您的代码很重要)会创建一些您必须手动编码的东西。您可能需要对操作进行排序,以便运行一个,然后在开始下一个之前等待它完成。或者,您可以启动所有三个操作,然后编写一些代码来收集所有三个结果,当它们都准备好时,
In modern Javascript, promises are generally used to manage these types of asynchronous operations.
在现代 Javascript 中,promise 通常用于管理这些类型的异步操作。
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:
在浏览器中的某些情况下可能会发生一种特殊情况。这不是真正的竞争条件,但如果您使用大量具有临时状态的全局变量,则可能需要注意。当您自己的代码导致另一个事件发生时,浏览器有时会同步调用该事件处理程序,而不是等到当前执行线程完成。这方面的一个例子是:
- click
- the click event handler changes focus to another field
- that other field has an event handler for onfocus
- browser calls the onfocus event handler immediately
- onfocus event handler runs
- the rest of the click event handler runs (after the .focus() call)
- 点击
- 单击事件处理程序将焦点更改为另一个字段
- 其他字段具有用于 onfocus 的事件处理程序
- 浏览器立即调用 onfocus 事件处理程序
- onfocus 事件处理程序运行
- 点击事件处理程序的其余部分运行(在 .focus() 调用之后)
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.
这在技术上不是竞争条件,因为 onfocus 事件处理程序何时执行(在.focus()
调用期间)是 100% 知道的。但是,它可能会造成一个事件处理程序运行而另一个正在执行中的情况。
回答by Jens
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.
重要的是要注意,如果您例如,您可能仍然会遇到竞争条件。使用多个异步 XMLHttpRequest。未定义返回响应的顺序(即响应可能不会以它们发送的相同顺序返回)。这里的输出取决于其他不可控事件(服务器延迟等)的顺序或时间。简而言之,这是一种竞争条件。
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.
因此,即使使用单个事件队列(如在 JavaScript 中)也不能阻止事件以无法控制的顺序出现,您的代码应该注意这一点。
回答by Vanuan
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).
如果用户在短时间内单击这 2 个按钮,则不能保证输出取决于单击顺序。这取决于哪个 api 请求将更快得到解决。此外,您引用的 DOM 元素可以被其他一些事件(例如更改路由)删除。
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:
要使其适应您的问题,这取决于“block()”是什么。如果是同步函数,则无需担心。但是如果它是异步的,你就必须担心:
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.
一个更实际的例子。考虑我们想要缓存 AJAX 请求。
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.
如果我们多次调用此代码会发生这种情况吗?我们不知道哪些数据会先返回,所以我们也不知道哪些数据会被缓存。前 2 次它将返回新数据,但第三次我们不知道要返回的响应的形状。