Node.js的循环与异步问题
(转自:http://bbs.tianya.cn/post-itinfo-280080-1.shtml)
Node.js 的异步机制由事件和回调函数实现,一开始接触可能会感觉违反常规,但习惯 以后就会发现还是很简单的。然而这之中其实暗藏了不少陷阱,一个很容易遇到的问题就是 循环中的回调函数,初学者经常容易陷入这个圈套。让我们从一个例子开始说明这个问题。
- var fs = require('fs');
- var files = ['a.txt', 'b.txt', 'c.txt'];
- for (var i = 0; i < files.length; i++) {
- fs.readFile(files[i], 'utf-8', function (err, contents) {
- console.log(files[i] + ': ' + contents);
- });
- }
这段代码的功能很直观,就是依次读取文件 a.txt、b.txt 、c.txt ,并输出文件名和内容。假设这三个文件的内容分别是 AAA 、BBB 和 CCC,那么我们期望的输出结果就是:
a.txt: AAA
b.txt: BBB
c.txt: CCC
可是我们运行这段代码的结果是怎样的呢?竟然是这样的结果:
undefined: AAA
undefined: BBB
undefined: CCC
这个结果说明文件内容正确输出了,而文件名却不对,也就意味着,contents 的结果是正确的,但 files[i] 的值是 undefined。这怎么可能呢,文件名不正确却能读取文件内容?既然难以直观地理解,我们就把 files[i] 分解并打印出来看看,在读取文件的回调函数中分别输出 files、i 和 files[i] 。
- var fs = require('fs');
- var files = ['a.txt', 'b.txt', 'c.txt'];
- for (var i = 0; i < files.length; i++) {
- fs.readFile(files[i], 'utf-8', function (err, contents) {
- console.log(files);
- console.log(i);
- console.log(files[i]);
- });
- }
运行修改后的代码,结果如下:
[ 'a.txt', 'b.txt', 'c.txt' ]
3
undefined
[ 'a.txt', 'b.txt', 'c.txt' ]
3
undefined
[ 'a.txt', 'b.txt', 'c.txt' ]
3
undefined
看到这里是不是有点启发了呢?三次输出的 i 的值都是 3 ,超出了 files 数组的下标范围,因此 files[i] 的值就是 undefined 了。这种情况通常会在 for 循环结束时发生,例如 for (var i = 0; i < files.length; i++),退出循环时 i 的值就files.length 的值。既然 i 的值是 3 ,那么说明了事实上 fs.readFile 的回调函数中访问到的 i 值都是循环退出以后的,因此不能分辨。而 files[i] 作为 fs.readFile 的第一个参数在循环中就传递了,所以文件可以被定位到,而且可以显示出文件的内容。
现在问题就明朗了:原因是3 次读取文件的回调函数事实上是同一个实例,其中引用到的 i 值是上面循环执行结束后的值,因此不能分辨。如何解决这个问题呢?我们可以利用
JavaScript 函数式编程的特性,手动建立一个闭包:
//forloopclosure.js
- var fs = require('fs');
- var files = ['a.txt', 'b.txt', 'c.txt'];
- for (var i = 0; i < files.length; i++) {
- (function (i) {
- fs.readFile(files[i], 'utf-8', function (err, contents) {
- console.log(files[i] + ': ' + contents);
- });
- })(i);
- }
上面代码在 for 循环体中建立了一个匿名函数,将循环迭代变量 i 作为函数的参数传递并调用。由于运行时闭包的存在,该匿名函数中定义的变量(包括参数表)在它内部的函数(fs.readFile 的回调函数)执行完毕之前都不会释放,因此我们在其中访问到的 i 就分别是不同的闭包实例,这个实例是在循环体执行的过程中创建的,保留了不同的值。
补充:闭包的写法,无法保证按数组存放文件顺序读取文件内容,相当多个文件读取操作并行进行,根据文件大小决定读取的快慢;而forEach是可以的保证顺序读取;
事实上以上这种写法并不常见,因为它降低了程序的可读性,故不推荐使用。大多数情况下我们可以用数组的 forEach 方法解决这个问题:
//callbackforeach.js
- var fs = require('fs');
- var files = ['a.txt', 'b.txt', 'c.txt'];
- files.forEach(function (filename) {
- fs.readFile(filename, 'utf-8', function (err, contents) {
- console.log(filename + ': ' + contents);
- });
- });
Node.js的循环与异步问题的更多相关文章
- Node.js 事件循环(Event Loop)介绍
Node.js 事件循环(Event Loop)介绍 JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”.Node.js在JavaScript的基 ...
- Node.js事件循环
Node JS是单线程应用程序,但它通过事件和回调概念,支持并发. 由于Node JS每一个API是异步的,作为一个单独的线程,它使用异步函数调用,以保持并发性.Node JS使用观察者模式.Node ...
- Node.js 事件循环机制
Node.js 采用事件驱动和异步 I/O 的方式,实现了一个单线程.高并发的 JavaScript 运行时环境,而单线程就意味着同一时间只能做一件事,那么 Node.js 如何通过单线程来实现高并发 ...
- 6、Node.js 事件循环
#########################################################################################Node.js 事件循 ...
- Node.js 事件循环
Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...
- Node.js 学习(五)Node.js 事件循环
Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...
- [译] 所有你需要知道的关于完全理解 Node.js 事件循环及其度量
原文地址:All you need to know to really understand the Node.js Event Loop and its Metrics 原文作者:Daniel Kh ...
- The Node.js Event Loop, Timers, and process.nextTick() Node.js事件循环,定时器和process.nextTick()
个人翻译 原文:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ The Node.js Event Loop, Ti ...
- 18.Node.js 事件循环
转自:http://www.runoob.com/nodejs/nodejs-tutorial.html Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node ...
随机推荐
- iOS UIPickerView 显示全国省市
效果图 #import "ViewController.h" @interface ViewController () @property(strong,nonatomic)UIP ...
- MVC+UnitOfWork+Repository+EF 之我见
UnitOfWork+Repository模式简介: 每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性.一致性.解决办法是:在Reposito ...
- Matlab代码优化--向量化
前段时间改写老师的一个计算纳米细颗粒物的C语言程序,不过运行的效率实在是太低了,工作站跑都很吃劲,晚上看了冈萨雷斯的数字图像处理意外发现了问题所在.在将C语言的程序改写成Matlab程序的时候我还是保 ...
- 瀑布流布局(jq实现)
参考刘晓帆大神的瀑布流效果 自己也写了个 希望能给打算写这个效果的朋友一点点思路,也希望朋友们批评指正! html代码: <!DOCTYPE html> <html> < ...
- 与考试相关的JS方法
var IsChange = 0;var ensure = 0;var timeCounter = (function () {//考试剩余时间 倒计时 var int; //var total = ...
- 105 董婷婷 第二次Sprint总结
总结: 第二次冲刺结束了,这次冲刺的主要任务是建立数据库.项目进行到现在也基本定型了,满满的成就感啊.经过一段时间的合作,团队成员间的默契大大提高,还有最后一次冲刺,队友们,加油哦!
- MyEclipse XFire Web Service
我们在做系统集成时,经常会需要调用webservice接口,本文将讲解在myeclipse中建立一个webservice项目,编写接口和实现类, 并且发布webservice,最后在myeclipse ...
- 各数据库查询前N条记录的SQL语句
sql在不同数据库查询前几条数据 1. ORACLE SELECT * FROM TABLE_NAME WHERE ROWNUM <= N; HQL: from table_name t or ...
- css的初步了解
学习了很多知识在这里,今天三月二十一日,老师讲了css的基础 对css有了初步的了解. 主要学习了以下几点: 一.css的选择器 1.派生选择器 2.类选择器 3.id选择器 4.属性选择器 二.cs ...
- springMVC 返回json 忽略类中属性的注解
该注解使用在 类名,接口头上 @JsonIgnoreProperties(value={"comid"}) //希望动态过滤掉的属性 该注解使用在get方法头上 @JsonIgno ...