NodeJS的异步编程风格

上传人:豆浆 文档编号:24902951 上传时间:2017-12-08 格式:PDF 页数:5 大小:114.57KB
返回 下载 相关 举报
NodeJS的异步编程风格_第1页
第1页 / 共5页
NodeJS的异步编程风格_第2页
第2页 / 共5页
NodeJS的异步编程风格_第3页
第3页 / 共5页
NodeJS的异步编程风格_第4页
第4页 / 共5页
NodeJS的异步编程风格_第5页
第5页 / 共5页
亲,该文档总共5页,全部预览完了,如果喜欢就下载吧!
资源描述

《NodeJS的异步编程风格》由会员分享,可在线阅读,更多相关《NodeJS的异步编程风格(5页珍藏版)》请在金锄头文库上搜索。

1、NodeJS运行环境因其支持Javascript语言和异步编程受到开发社区越来越多的关注。从GitHub上的访问量来看,NodeJS项目的关注度在最近几个月已经超过了Ruby及RoR。作为一个新鲜的平台,开发人员开始尝试去接触并运用于实际工作中,比如LinkedIn、Yammer、GitHub、淘宝等企业已经在生产环境中部署了NodeJS应用。不过,在学习NodeJS的过程中,从同步编程到异步编程风格的转换是开发人员面临的一个主要问题,我们如何去适应呢?技术社区在讨论这种转变,专家Marc Fasel也撰写了精彩的文章来阐述该问题,本文尝试结合Marc Fasel的指导思想和笔者的实践经验来介

2、绍一些NodeJS的异步编程风格,希望对NodeJS的初学者有所启发。 第一个例子,读取目录信息 说起NodeJS的异步编程,我们必须提到回调函数(callback),纵览NodeJS的API文档,满眼的回调函数说明,在其他的编程语言中,也会存在一些异步的回调函数模型,但是没有NodeJS这样的大范围应用。这些回调函数应用在异步函数中,作为其参数,当异步函数触发某事件时(如http响应返回)即调用该回调函数做进一步操作。NodeJS也提供了一些传统的同步函数,即应用程序必须等待该函数返回,才会执行后面的代码。而异步函数则不同,应用程序在调用异步函数后会立即返回,执行后面的代码,至于异步函数的处

3、理则交给回调函数来做。例如,在NodeJS中存在两个获取目录信息的函数,分别是同步的readdirSync()和异步的readdir()。看下面的代码片段(源于Marc Fasel,略作改动): /同步 filenames = fs.readdirSync(.); for (i = 0; i filenames.length; i+) console.log(filenamesi); console.log(Current uid: + process.getuid(); /异步 fs.readdir(., function (err, filenames) var i; for (i = 0

4、; i filenames.length; i+) console.log(filenamesi); ); console.log(Current uid: + process.getuid(); 请注意看,在同步函数的代码中,没有什么特别之处,应用程序会按顺序打印当前目录包含的文件名,然后再打印当前进程的用户ID,其实际运行结果也如我们所料。而在异步函数的代码中,我们把打印文件名的代码放在了readdir函数参数里的回调函数中,这样当readdir获取目录信息之后就调用该回调函数打印文件名。但是应用程序在调用了异步函数fs.readdir(., function (err, filename

5、s) )之后,会立即执行后面打印进程用户ID的代码,不会停下来等待readdir函数返回。这就是异步与同步的差别,实际的运行结果也与之前不同,异步函数的执行和回调函数的处理总需要一些时间,所以在很大程度上应用程序会首先打印出进程用户ID,再打印出文件名。在通常的测试环境中,结果也是这样。从这个例子中,我们可以学到两点:一是在异步编程中,需要把依赖于异步函数(需要其执行结果或者达到某种状态)的代码放在对应的回调函数中;二是异步函数后面的代码会立即执行,所以在编程时需要通盘考虑,以免出现意外之外的运行结果。 第二个例子,统计所有文件字节数 刚才的例子是一个简单的顺序执行逻辑,如果异步函数包含在循环

6、中会是什么样子?就会出现若干异步函数在并发运行的情况,开发人员需要这些异步函数共同完成一项任务的话,如何协作? 看到这里,读者的脑海里可能会马上浮现出其他编程语言中线程并发的代码。现在来看第二个NodeJS示例,计算当前目录中所有文件占用的总字节数。该例子用到的是同步函数statSync()和异步函数stat(),它们可以获取文件的基本信息。先来看看各自的代码片段(源于Marc Fasel,略作改动): /同步 filenames = fs.readdirSync(.); for (i = 0; i filenames.length; i +) stats = fs.statSync(./ +

7、 filenamesi); 页码,1/5 totalBytes += stats.size; console.log(totalBytes); /异步 count = filenames.length; for (i = 0; i filenames.length; i+) fs.stat(./ + filenamesi, function (err, stats) totalBytes += stats.size; count-; if (count = 0) console.log(totalBytes); ); 同步函数的例子符合开发人员的传统编程风格,清晰明了。在for循环中,stat

8、Sync被依次调用,占用字节数也顺序累加,循环结束后打印出统计结果。如果换成异步函数stat()会怎样?在上一个例子中我们讲到,把依赖异步调用结果的代码放到回调函数中,我们也正是这样做的。但是仅做到这一步还不够。对比同步和异步的例子,会发现多了一些有关count的语句。如果我们把这些语句先注释掉,同时按照同步编程的逻辑将打印结果的代码放到循环后面,运行结果就是很可能输出的字节数为totalBytes的初始值。因为按照异步函数的原理,for循环依次调用stat()之后,会立即执行后面的代码即打印结果,此时若干个异步函数很可能还没完成。这就是我们需要count语句的原因。在多个相同异步函数协作的情

9、况下,代码需要引入计数变量来检测所有异步函数的退出。在正确的异步代码中,count在for循环之前设置为目录下文件的数量,即回调函数调用的次数。当回调函数被调用时(即某个文件的基本信息已经获取),totalBytes累加该文件的字节数,同时count减一,表示该文件已经被统计在内。由于多个异步函数在并发运行,难以判断谁先返回。所以在这里加入了一个if判断语句,如果此时count等于0,那么意味着所有的文件(回调函数)都累加完毕,那么当前的回调函数就是最后执行的,它负责输出总字节数。这种代码手法类似于其他语言中的线程协作的例子,相比之下,Javascript语言的闭包特性使得NodeJS的异步编

10、程更容易,示例代码中的回调函数可以访问函数之外的count变量和totalBytes,无需特殊处理。从这个例子中,我们可以学到一点:并发运行的相同异步函数如果协作完成任务,需要添加计数代码判断执行状态,并且把所有异步函数完成后执行的代码放在判断条件的语句块里。 第三个例子,访问网站内容 在讨论第三个例子之前,我们先来看一下NodeJS的事件触发机制。NodeJS引擎中许多对象都有预定的事件,如用户在发送http请求之后获得的http.ServerRequest对象就有data和end两个事件,其中data指接收到响应信息正文中的一部分时会触发此事件,end指完全接收完信息后都会触发一次。开发人

11、员如果想处理响应,则需要注册回调函数,如下列代码片段: response.on(data, function (chunk) ); response.on(end,function(); 第三个例子的使用场景是:访问某网站,分析其页面内容,然后根据判断条件来决定继续访问下一页并做同样的分析还是在本页面停止(即退出应用)。首先采用NodeJS的HTTP模块编写第一次访问页面并分析内容的代码,代码框架如下: var hostRequest = http.request(requestOptions,function(response) response.on(data, function (chu

12、nk) responseHTML = responseHTML + chunk;/累加响应正文 ); response.on(end,function() console.log(responseHTML); /分析页面内容 ); 页码,2/5); hostRequest.end(); 从上面的代码中,我们可以看到NodeJS编程的常见风格,就是异步函数套异步函数。在回调函数里,利用异步函数传入的参数做业务处理,往往还需要在内部继续定义回调函数,这样做的好处是可以利用闭包特性来访问外部的变量等。下面我们来看end事件的回调函数,其中要分析页面内容,如果需要访问下一页的话,上面的代码是可以复用的

13、,毕竟功能是一样的,都是访问页面然后分析,那么如何重用呢?在传统的编程中,开发人员会想到使用一个while循环,根据判断条件调用break语句退出,可能的代码如下: /错误的代码 while(true) hostRequest = http.request(requestOptions,function(response) response.on(data, function (chunk) responseHTML = responseHTML + chunk; ); response.on(end,function() console.log(responseHTML); /分析页面内容

14、if(canStop) break; ); ); hostRequest.end(); 这种直接按照传统思路编写的代码是完全错误的。感兴趣的读者可以尝试运行该段代码,NodeJS会抛出“溢出”之类的错误。究其原因,这段代码沿用了同步顺序执行的老办法,而实际在运行中,while循环会瞬间产生大量的http请求,而不会等待每个循环中设置的回调函数返回。而且,在end事件对应的回调函数中调用的break语句并不会影响while循环,因为它处于回调函数中,“函数套函数”的编程风格有时会让开发人员误把内部函数的代码当成了外部函数的内容。 现在让我们比较一下第二、三个例子之间的区别。第二个例子是开发人员希

15、望并发运行异步函数,而第三个例子则要求顺序执行异步函数,为的是复用代码。NodeJS在HTTP模块提供的都是异步函数,不像是File System模块那样提供了同步和异步的函数对。在这种情况下,我们该怎么办呢? 熟悉Javascript异步编程的读者可能会从第二个例子中得到启发,想到用setInterval()再加上状态变量来定时判断当前页面的http响应是否处理完毕,而NodeJS的确提供了Timers模块,我们来看一下代码片段: var previousFinished = true; var intervalId= setInterval(FindPageItems,1000); fun

16、ction FindPageItems() if(previousFinished = false) /myLog(wait for ready); return; previousFinished = false; hostRequest = http.request(requestOptions,function(response) response.on(data, function (chunk) responseHTML = responseHTML + chunk; ); response.on(end,function() console.log(responseHTML); 页码,3/5 /

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 商业/管理/HR > 其它文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号