异步 Javascript 执行是如何发生的?什么时候不使用 return 语句?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7104474/
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
How does Asynchronous Javascript Execution happen? and when not to use return statement?
提问by ekanna
// synchronous Javascript
var result = db.get('select * from table1');
console.log('I am syncronous');
// asynchronous Javascript
db.get('select * from table1', function(result){
// do something with the result
});
console.log('I am asynchronous')
I know in synchronous code, console.log() executes after result is fetched from db, whereas in asynchronous code console.log() executes before the db.get() fetches the result.
我知道在同步代码中,console.log() 在从 db 获取结果后执行,而在异步代码中 console.log() 在 db.get() 获取结果之前执行。
Now my question is, how does the execution happen behind the scenes for asynchronous code and why is it non-blocking?
现在我的问题是,异步代码的执行是如何在幕后发生的,为什么它是非阻塞的?
I have searched the Ecmascript 5 standard to understand how asynchronous code works but could not find the word asynchronous in the entire standard.
我搜索了 Ecmascript 5 标准以了解异步代码的工作原理,但在整个标准中找不到异步这个词。
And from nodebeginner.org I also found out that we should not use a return statement as it blocks the event loop. But nodejs api and third party modules contain return statements everywhere. So when should a return statement be used and when shouldn't it?
从 nodebeginner.org 我还发现我们不应该使用 return 语句,因为它会阻塞事件循环。但是 nodejs api 和第三方模块到处都包含 return 语句。那么什么时候应该使用 return 语句,什么时候不应该呢?
Can somebody throw some light on this?
有人可以对此有所了解吗?
采纳答案by jfriend00
First of all, passing a function as a parameter is telling the function that you're calling that you would like it to call this function some time in the future. When exactly in the future it will get called depends upon the nature of what the function is doing.
首先,将函数作为参数传递是告诉您正在调用的函数,您希望它在将来的某个时间调用此函数。确切地说,它何时会被调用取决于函数正在执行的操作的性质。
If the function is doing some networking and the function is configured to be non-blocking or asychronous, then the function will execute, the networking operation will be started and the function you called will return right away and the rest of your inline javascript code after that function will execute. If you return a value from that function, it will return right away, long before the function you passed as a parameter has been called (the networking operation has not yet completed).
如果该函数正在执行一些网络操作并且该函数被配置为非阻塞或异步,则该函数将执行,网络操作将开始,您调用的函数将立即返回,其余的内联 javascript 代码将在之后该函数将执行。如果您从该函数返回一个值,它将立即返回,早在您作为参数传递的函数被调用之前(网络操作尚未完成)。
Meanwhile, the networking operation is going in the background. It's sending the request, listening for the response, then gathering the response. When the networking request has completed and the response has been collected, THEN and only then does the original function you called call the function you passed as a parameter. This may be only a few milliseconds later or it may be as long as minutes later - depending upon how long the networking operation took to complete.
同时,联网操作正在后台进行。它发送请求,监听响应,然后收集响应。当网络请求完成并收集到响应后,然后您调用的原始函数才会调用您作为参数传递的函数。这可能只是几毫秒后,也可能长达几分钟 - 取决于网络操作完成所需的时间。
What's important to understand is that in your example, the db.get()
function call has long since completed and the code sequentially after it has also executed. What has not completed is the internal anonymous function that you passed as a parameter to that function. That's being held in a javascript function closure until later when the networking function finishes.
重要的是要理解,在您的示例中,db.get()
函数调用早已完成,并且代码也在执行之后按顺序执行。尚未完成的是您作为参数传递给该函数的内部匿名函数。这被保存在 javascript 函数闭包中,直到稍后网络函数完成。
It's my opinion that one thing that confuses a lot of people is that the anonymous function is declared inside of your call to db.get and appears to be part of that and appears that when db.get()
is done, this would be done too, but that is not the case. Perhaps that would look less like that if it was represented this way:
我认为让很多人感到困惑的一件事是匿名函数是在您对 db.get 的调用中声明的,并且似乎是其中的一部分,并且似乎db.get()
完成后也会这样做,但那是不是这样。如果它以这种方式表示,也许看起来就不那么像了:
function getCompletionfunction(result) {
// do something with the result of db.get
}
// asynchronous Javascript
db.get('select * from table1', getCompletionFunction);
Then, maybe it would be more obvious that the db.get will return immediately and the getCompletionFunction will get called some time in the future. I'm not suggesting you write it this way, but just showing this form as a means of illustrating what is really happening.
然后,也许更明显的是 db.get 将立即返回并且 getCompletionFunction 将在未来某个时间被调用。我不是建议你这样写,而只是展示这种形式,作为说明真实情况的一种方式。
Here's a sequence worth understanding:
这是一个值得理解的序列:
console.log("a");
db.get('select * from table1', function(result){
console.log("b");
});
console.log("c");
What you would see in the debugger console is this:
您将在调试器控制台中看到的是:
a
c
b
"a" happens first. Then, db.get() starts its operation and then immediately returns. Thus, "c" happens next. Then, when the db.get() operation actually completes some time in the future, "b" happens.
“a”首先出现。然后,db.get() 开始其操作,然后立即返回。因此,“c”接下来发生。然后,当 db.get() 操作在未来某个时间实际完成时,“b”发生。
For some reading on how async handling works in a browser, see How does JavaScript handle AJAX responses in the background?
有关异步处理如何在浏览器中工作的一些阅读材料,请参阅JavaScript 如何在后台处理 AJAX 响应?
回答by icktoofay
jfriend00's answerexplains asynchrony as it applies to most users quite well, but in your comment you seemed to want more details on the implementation:
jfriend00 的回答解释了异步,因为它很好地适用于大多数用户,但在您的评论中,您似乎想要更多关于实现的细节:
[…] Can any body write some pseudo code, explaining the implementation part of the Ecmascript specification to achieve this kind of functionality? for better understanding the JS internals.
[…] 任何机构都可以编写一些伪代码,解释 Ecmascript 规范的实现部分来实现这种功能吗?为了更好地理解 JS 内部结构。
As you probably know, a function can stow away its argument into a global variable. Let's say we have a list of numbers and a function to add a number:
您可能知道,函数可以将其参数存放到全局变量中。假设我们有一个数字列表和一个添加数字的函数:
var numbers = [];
function addNumber(number) {
numbers.push(number);
}
If I add a few numbers, as long as I'm referring to the same numbers
variable as before, I can access the numbers I added previously.
如果我添加几个数字,只要我指的是与numbers
以前相同的变量,我就可以访问我之前添加的数字。
JavaScript implementations likely do something similar, except rather than stowing numbers away, they stow functions (specifically, callback functions) away.
JavaScript 实现可能会做类似的事情,除了它们不是将数字存放起来,而是将函数(特别是回调函数)存放起来。
The Event Loop
事件循环
At the core of many applications is what's known as an event loop. It essentially looks like this:
许多应用程序的核心是所谓的事件循环。它基本上是这样的:
- loop forever:
- get events, blocking if none exist
- process events
- 永远循环:
- 获取事件,如果不存在则阻塞
- 过程事件
Let's say you want to execute a database query like in your question:
假设您想像您的问题一样执行数据库查询:
db.get("select * from table", /* ... */);
In order to perform that database query, it will likely need to perform a network operation. Since network operations can take a significant amount of time, during which the processor is waiting, it makes sense to think that maybe we should, rather than waiting rather than doing some other work, just have it tell us when it's done so we can do other things in the mean time.
为了执行该数据库查询,它可能需要执行网络操作。由于网络操作可能需要大量时间,在此期间处理器正在等待,因此认为也许我们应该,与其等待而不是做一些其他工作,不如让它告诉我们何时完成,以便我们可以这样做是有道理的在此期间的其他事情。
For simplicity's sake, I'll pretend that sending will never block/stall synchronously.
为简单起见,我假设发送永远不会同步阻塞/停止。
The functionality of get
might look like this:
的功能get
可能如下所示:
- generate unique identifier for request
- send off request (again, for simplicity, assuming this doesn't block)
- stow away (identifier, callback) pair in a global dictionary/hash table variable
- 为请求生成唯一标识符
- 发送请求(再次,为简单起见,假设这不会阻止)
- 在全局字典/哈希表变量中存放(标识符、回调)对
That's all get
would do; it doesn't do any of the receiving bit, and it itself isn't responsible for calling your callback. That happens in the process events bit. The process events bit might look (partially) like this:
这就是全部get
;它不做任何接收位,它本身也不负责调用你的回调。这发生在过程事件位。流程事件位可能(部分)如下所示:
- is the event a database response? if so:
- parse the database response
- look up the identifier in the response in the hash table to retrieve the callback
- call the callback with the received response
- 事件是数据库响应吗?如果是这样的话:
- 解析数据库响应
- 在哈希表中查找响应中的标识符以检索回调
- 使用收到的响应调用回调
Real Life
现实生活
In real life, it's a little more complex, but the overall concept is not too different. If you want to send data, for example, you might have to wait until there's enough space in the operating system's outgoing network buffers before you can add your bit of data. When reading data, you might get it in multiple chunks. The process events bit probably isn't one big function, but itself just calling a bunch of callbacks (which then dispatch to more callbacks, and so on…)
在现实生活中,它有点复杂,但总体概念并没有太大的不同。例如,如果您想发送数据,您可能必须等到操作系统的传出网络缓冲区中有足够的空间才能添加您的数据位。读取数据时,您可能会以多个块的形式获取它。进程事件位可能不是一个大函数,但它本身只是调用一堆回调(然后调度到更多回调,依此类推……)
While the implementation details between real life and our example are slightly different, the concept is the same: you kick off ‘doing something', and a callback will be called through some mechanism or another when the work is done.
虽然现实生活和我们的示例之间的实现细节略有不同,但概念是相同的:您开始“做某事”,当工作完成时,将通过某种机制调用回调。