异步 Javascript 执行是如何发生的?啥时候不使用return语句?

Posted

技术标签:

【中文标题】异步 Javascript 执行是如何发生的?啥时候不使用return语句?【英文标题】:How does Asynchronous Javascript Execution happen? and when not to use return statement?异步 Javascript 执行是如何发生的?什么时候不使用return语句? 【发布时间】:2011-10-29 14:07:05 【问题描述】:
// 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')

我知道在同步代码中,console.log() 在从 db 获取结果之后执行,而在异步代码中,console.log() 在 db.get() 获取结果之前执行。

现在我的问题是,异步代码的执行是如何在后台发生的,为什么它是非阻塞的?

我搜索了 Ecmascript 5 标准以了解异步代码的工作原理,但在整个标准中找不到异步这个词。

从 nodebeginner.org 我还发现我们不应该使用 return 语句,因为它会阻塞事件循环。但是 nodejs api 和第三方模块到处都包含 return 语句。那么什么时候应该使用 return 语句,什么时候不应该呢?

有人可以解释一下吗?

【问题讨论】:

这篇文章的一些好答案:***.com/questions/2035645/… 【参考方案1】:

首先,将函数作为参数传递是告诉函数您正在调用您希望它在将来的某个时间调用此函数。未来何时调用它取决于函数正在执行的操作的性质。

如果该函数正在做一些联网并且该函数被配置为非阻塞或异步,那么该函数将执行,联网操作将启动,您调用的函数将立即返回,其余的内联该函数之后的javascript代码将执行。如果您从该函数返回一个值,它将立即返回,早在您作为参数传递的函数被调用之前(网络操作尚未完成)。

与此同时,网络操作正在后台进行。它发送请求,监听响应,然后收集响应。当网络请求完成并收集响应时,然后,您调用的原始函数才会调用您作为参数传递的函数。这可能仅在几毫秒之后,也可能在几分钟之后 - 取决于网络操作完成所需的时间。

需要了解的重要一点是,在您的示例中,db.get() 函数调用早已完成,并且代码在其执行后也按顺序执行。尚未完成的是您作为参数传递给该函数的内部匿名函数。它被保存在一个 javascript 函数闭包中,直到稍后网络函数完成。

我认为让很多人感到困惑的一件事是,匿名函数是在您对 db.get 的调用中声明的,并且似乎是其中的一部分,并且当db.get() 完成时,这将是也做了,但事实并非如此。如果以这种方式表示,也许会看起来不那么像:

function getCompletionfunction(result) 
    // do something with the result of db.get


// asynchronous Javascript 
db.get('select * from table1', getCompletionFunction);

那么,也许更明显的是 db.get 将立即返回,而 getCompletionFunction 将在未来某个时间被调用。我不是建议你这样写,只是展示这个表格来说明实际发生的事情。

这是一个值得理解的序列:

console.log("a");
db.get('select * from table1', function(result)
    console.log("b");
);
console.log("c");

您将在调试器控制台中看到的是:

a
c
b

“a”首先发生。然后,db.get() 开始它的操作,然后立即返回。因此,“c”接下来发生。然后,当 db.get() 操作在未来某个时间实际完成时,会发生“b”。


有关异步处理如何在浏览器中工作的一些信息,请参阅How does JavaScript handle AJAX responses in the background?

【讨论】:

"需要理解的是,在您的示例中,db.get() 函数调用早已完成,并且在它执行之后的代码也依次执行。没有完成的是内部匿名函数你作为参数传递给该函数。它被保存在一个 javascript 函数闭包中,直到稍后网络函数完成。这是如何实现的。任何人都可以写一些伪代码,解释 Ecmascript 规范的实现部分来实现这种功能吗?为了更好地理解 JS 内部结构。【参考方案2】:

jfriend00's answer 解释了异步,因为它很好地适用于大多数用户,但在您的评论中,您似乎想要更多关于实现的细节:

[...] 任何人都可以写一些伪代码,解释 Ecmascript 规范的实现部分来实现这种功能吗?为了更好地理解 JS 内部结构。

您可能知道,函数可以将其参数存储到全局变量中。假设我们有一个数字列表和一个添加数字的函数:

var numbers = [];
function addNumber(number) 
    numbers.push(number);

如果我添加一些数字,只要我引用与以前相同的 numbers 变量,我就可以访问我之前添加的数字。

JavaScript 实现可能会做类似的事情,只不过它们不是将数字存储起来,而是将函数(特别是回调函数)存储起来。

事件循环

许多应用程序的核心是所谓的事件循环。它基本上看起来像这样:

永远循环: 获取事件,如果不存在则阻塞 处理事件

假设你想像你的问题一样执行一个数据库查询:

db.get("select * from table", /* ... */);

为了执行该数据库查询,它可能需要执行网络操作。由于网络操作可能需要大量时间,在此期间处理器正在等待,因此认为也许我们应该而不是等待而不是做一些其他工作是有意义的,只是让它告诉我们什么时候完成,这样我们就可以做同时做其他事情。

为简单起见,我假设发送永远不会同步阻塞/停止。

get 的功能可能如下所示:

为请求生成唯一标识符 发送请求(再次,为简单起见,假设这不会阻塞) 在全局字典/哈希表变量中存储(标识符、回调)对

这就是get 所做的一切;它不做任何接收位,它本身也不负责调用你的回调。这发生在进程事件位中。进程事件位可能(部分)如下所示:

事件是数据库响应吗?如果是这样的话: 解析数据库响应 在哈希表的响应中查找标识符以检索回调 使用收到的响应调用回调

现实生活

在现实生活中,它有点复杂,但整体概念并没有太大的不同。例如,如果您想发送数据,您可能必须等到操作系统的传出网络缓冲区中有足够的空间后才能添加您的数据。读取数据时,您可能会以多个块的形式获取它。进程事件位可能不是一个大函数,但它本身只是调用一堆回调(然后调度到更多回调,等等......)

虽然现实生活和我们的示例之间的实现细节略有不同,但概念是相同的:您开始“做某事”,当工作完成时,将通过某种机制调用回调。

【讨论】:

以上是关于异步 Javascript 执行是如何发生的?啥时候不使用return语句?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript异步机制

Javascript异步编程方法之------“事件监听”

Angular中的变更检测

前端技术 | JavaScript 与 异步编程

关于javascript异步

如何学好Javascript的异步编程