当前位置:嗨网首页>书籍在线阅读

09-事件队列(循环)

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

[toc]

2.3.1 事件队列(循环)

为了在应用程序中实现异步功能,可以采取以下两种方式之一。一种方式是将线程分配给每个耗时的进程,其余代码则并行运行。这个方法的问题在于线程很昂贵。它们不仅耗费资源,还会增加应用的复杂度。

第二种方法是采用事件驱动架构。在这种情况下,当耗时进程被调用时,应用并不会等它完成。相反,进程会通过触发一个事件来表示其结束。该事件会被添加到队列或事件循环中。所有依赖于这个事件的函数都会针对该事件注册一个监听函数,当事件被从事件循环中取出和处理时,依赖于这个事件的函数就会执行,同时系统会将事件相关的所有数据发给这个函数。

浏览器和Node中的JavaScript都采取了第二种方法。在浏览器中,如果在一个元素上添加一个 click 处理函数,实际上是注册(或订阅)了一个事件并且提供一个回调函数,当事件发生时调用这个回调函数,并让程序的其他部分继续运行。

<div id="someid"> </div>
<script>
   document.getElementById("someid").addEventListener("click",
                                        function(event) {
       event.target.innerHTML = "I been clicked!";
   }, false);
</script>

Node有自己的事件循环,但它不会去等诸如元素的点击事件这样的UI事件,而是去实现服务端的一些功能,主要是输入和输出(I/O)。其中包括一些跟文件相关的事件,比如打开文件,在过程完成的时候触发事件;将文件内容读入缓冲器,并在过程完成的时候通过触发事件通知客户端;或等待用户发出的Web请求。这些操作可能会消耗很多时间和资源。对资源的每次访问,都会将资源锁定从而禁止其他访问,直到访问结束。此外,基于Web的应用程序依赖于用户操作,有时也依赖其他应用程序的操作。

Node会按顺序处理事件队列中的所有事件。当遇到你感兴趣的事件时,它会调用你提供的回调函数,并传入与该事件相关的所有信息。

在第1章中我们创建了一个基本的Web服务器作为本书的第一个例子,其中就有事件循环。这里列出了之前的代码,以方便大家阅读:

var http = require('http');
http.createServer(function (request, response) {
  response.writeHead(200, {'Content-Type': 'text/plain'});
  response.end('Hello World\n');
}).listen(8124);
console.log('Server running at http://127.0.0.1:8124/')
;

在例2-4中,我修改了代码以将独立操作分解开来,并且增加了对在服务器创建、客户端连接过程中发生的事件和进程事件的监听。

例2-4 包括额外事件处理的基本Web服务器

var http = require('http');
var server = http.createServer();
server.on('request', function (request, response) {
   console.log('request event');
   response.writeHead(200, {'Content-Type': 'text/plain'});
   response.end('Hello World\n');
});
server.on('connection', function() {
   console.log('connection event');
});
server.listen(8124, function() {
   console.log('listening event');
});
console.log('Server running on port 8124');

注意, requestListener() 函数也就是服务器请求回调函数,并没有在 http. createServer() 中被调用。相反,应用程序将新创建的HTTP服务器赋值给一个变量,然后用它来捕获以下两个事件:

  • 每次客户端发出Web请求时触发的 request 事件;
  • 每次新客户端连接到Web应用程序时触发的 connection 事件。

在这两种情况下,事件都是用 on() 函数订阅的(HTTP的 server 类从 EventEmitter 类继承了这个方法)。我将在下一节中介绍这个父类,但现在,我们先来关注事件本身。在上面的示例中还有一个事件订阅,就是listening事件,它可以通过HTTP的 server.listen() 函数上的回调函数进行访问。

我们现在有了一个对象(HTTP服务器)和3个事件( requestconnectionlistening )。那么当应用程序被创建的时候和Web请求被发出的时候会发生什么事?

启动应用程序的时候,“ Server is running on port 8124. ”的消息会立即被打印出来。这是因为不管是在创建服务器、连接客户端,或者当我们开始监听请求时,应用程序都不会被阻塞。所以,第一次 console.log() 的消息实际上是在所有非阻塞异步函数执行之后完成的。

接下来的消息是“ listening event. ”。一旦创建了服务器,我们就要监听新的连接和请求,这件事是通过调用 server.listen() 函数来完成的。另外,不需要等待任何“服务器已创建”事件,因为 http.createServer () 函数会立即返回。你可以通过在调用 http.createServer() 函数之后直接插入 console.log() 消息来进行测试。如果加上这一行代码,那么当启动应用程序时,它就会被打印到控制台。

在这个程序的上一个版本中, server.listen() 是在 http.createServer() 函数之后被连续调用的,但这不是必须的。这样做只是为了更方便、让编程优雅,而它不是实现事件驱动的唯一写法。但是, server.listen() 函数是一个具有回调的异步函数,其回调函数会在 listening 事件发出时被调用。因此,控制台中的消息会在“ server is running on port 8124 ”这个消息之后显示。

在客户端与Web应用程序连接之前,不会打印任何其他消息。然后,我们会收到 Connection 事件的消息,因为“ Connection ”是每个新客户端调用的第一个事件。接着是一个或两个 request 事件的消息。 request 事件的数量之所以不同,是因为每个浏览器向新网站发请求的方式不同。Chrome在发请求时,不仅会请求资源,而且会请求favicon.ico文件,所以应用程序会收到两个请求。Firefox和IE只请求资源,并不请求其他内容,所以应用程序只会收到一个请求。

如果在同一浏览器中刷新页面,那么只会收到 request 事件的消息。连接是早已经建立好的,会一直保持下去,直到用户关闭浏览器或发生超时(timeout)。使用不同的浏览器访问相同的资源时,会为每个浏览器触发一个单独的 Connection 事件。

用Chrome访问Web应用程序会在控制台中打印出以下信息:

  • Server running on port 8124;
  • Listening event;
  • Connection event;
  • Request event;
  • Request event。

如果你想把模块中或者程序中的函数改为异步函数,就需要使用特定的标准来定义它,我将在下面介绍这些内容。