asynchronousJavaScript执行如何发生? 什么时候不用return语句?

// 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中获取结果之后执行,而在asynchronous代码中,console.log()在db.get()获取结果之前执行。

现在我的问题是,如何执行asynchronous代码的幕后执行,为什么它是非阻塞的?

我已经search了Ecmascript 5标准来理解asynchronous代码是如何工作的,但在整个标准中找不到asynchronous字。

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

有人可以指出这一点吗?

首先,传递一个函数作为参数告诉你正在调用的函数,希望将来有一段时间调用这个函数。 当将来它将被调用取决于函数的性质。

如果该function正在做一些联网,并且function被configuration为非阻塞或asynchronous,那么该function将被执行,联网操作将被启动,并且您调用的函数将立即返回,其余的内联JavaScript代码该函数将执行。 如果你从这个函数返回一个值,那么在你作为parameter passing的函数被调用(联网操作还没有完成)很久之前,它将立即返回。

同时,networking运营正在后台进行。 它发送请求,收听响应,然后收集响应。 当networking请求完成并收集到响应时,那么只有当您调用的原始函数调用您作为parameter passing的函数时。 这可能只有几毫秒之后,也可能是几分钟后 – 取决于networking操作完成的时间长短。

重要的是要理解的是,在你的例子中, db.get()函数调用已经完成了很长时间,并且代码在执行之后依次执行。 没有完成的是作为parameter passing给该函数的内部匿名函数。 这是closures的JavaScript函数closures,直到后来当networkingfunction完成。

我认为,让很多人感到困惑的是匿名函数是在你调用db.get的时候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将在未来一段时间被调用。 我并不是build议你这样写,而只是把这个表格作为说明真实情况的手段。

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

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

你会看到在debugging器控制台是这样的:

 a c b 

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


有关阅读asynchronous处理在浏览器中的工作原理,请参阅JavaScript如何在后台处理AJAX响应?

jfriend00的答案解释了asynchronous,因为它适用于大多数用户相当好,但在您的评论中,你似乎想要更多的细节实施:

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

正如你可能知道的,一个函数可以把它的参数存储到一个全局variables中。 比方说,我们有一个数字列表和一个函数来添加一个数字:

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

如果我添加几个数字,只要我指的是与以前相同的numbersvariables,我可以访问我以前添加的数字。

JavaScript实现可能会做类似的事情,除了存储数字之外,它们将函数(特别是callback函数)存储起来。

事件循环

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

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

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

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

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

为了简单起见,我会假装发送不会同步阻塞/失速。

get的function可能如下所示:

  • 生成请求的唯一标识符
  • 发送请求(再次,为了简单起见,假设这不会阻止)
  • 在全局字典/哈希表variables中collections(标识符,callback)对

这一切都会做; 它没有做任何接收位,它本身不负责调用你的callback。 这发生在进程事件位。 进程事件位可能看起来(部分)如下所示:

  • 数据库响应是事件吗? 如果是这样:
    • parsing数据库响应
    • 在哈希表中的响应中查找标识符来检索callback
    • 使用收到的响应调用callback

现实生活

在现实生活中,情况稍微复杂一些,但总体观念并没有太大的差别。 例如,如果要发送数据,则可能必须等到操作系统的传出networking缓冲区中有足够的空间后才能添加数据。 读取数据时,可能会在多个块中获取。 进程事件位可能不是一个大function,但它本身只是调用一堆callback(然后调度到更多的callback,等等…)

虽然现实生活与我们的例子之间的实现细节略有不同,但是概念是一样的:您开始“做某事”,并且在完成工作时通过某种机制或某种机制调用callback。