(转自:http://bbs.tianya.cn/post-itinfo-280080-1.shtml)

Node.js 的异步机制由事件和回调函数实现,一开始接触可能会感觉违反常规,但习惯  以后就会发现还是很简单的。然而这之中其实暗藏了不少陷阱,一个很容易遇到的问题就是  循环中的回调函数,初学者经常容易陷入这个圈套。让我们从一个例子开始说明这个问题。

  1. var fs = require('fs');
  2. var files = ['a.txt', 'b.txt', 'c.txt'];
  3.  
     
  4. for (var i = 0; i < files.length; i++) {
  5.   fs.readFile(files[i], 'utf-8', function (err, contents) {
  6.   console.log(files[i] + ': ' + contents);
  7.   
     
  8.     });
  9. }

这段代码的功能很直观,就是依次读取文件 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] 。

  1. var fs = require('fs');
  2. var files = ['a.txt', 'b.txt', 'c.txt'];
  3. for (var i = 0; i < files.length; i++) {
  4.     fs.readFile(files[i], 'utf-8', function (err, contents) {
  5.         console.log(files);
  6.         console.log(i);
  7.         console.log(files[i]);
  8.     });
  9. }

 

  运行修改后的代码,结果如下:

  [ '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

  1. var fs = require('fs');
  2.   
     
  3. var files = ['a.txt', 'b.txt', 'c.txt'];
  4.  
  5. for (var i = 0; i < files.length; i++) {
  6.   (function (i) {
  7.   fs.readFile(files[i], 'utf-8', function (err, contents) {
  8.   console.log(files[i] + ': ' + contents);
  9.         });
  10.     })(i);
  11. }

上面代码在 for 循环体中建立了一个匿名函数,将循环迭代变量 i 作为函数的参数传递并调用。由于运行时闭包的存在,该匿名函数中定义的变量(包括参数表)在它内部的函数(fs.readFile 的回调函数)执行完毕之前都不会释放,因此我们在其中访问到的 i 就分别是不同的闭包实例,这个实例是在循环体执行的过程中创建的,保留了不同的值。

    补充:闭包的写法,无法保证按数组存放文件顺序读取文件内容,相当多个文件读取操作并行进行,根据文件大小决定读取的快慢;而forEach是可以的保证顺序读取;

事实上以上这种写法并不常见,因为它降低了程序的可读性,故不推荐使用。大多数情况下我们可以用数组的 forEach 方法解决这个问题:

  //callbackforeach.js

  1. var fs = require('fs');
  2. var files = ['a.txt', 'b.txt', 'c.txt'];
  3. files.forEach(function (filename) {
  4.   fs.readFile(filename, 'utf-8', function (err, contents) {
  5.   console.log(filename + ': ' + contents);
  6.     });
  7. });

Node.js的循环与异步问题的更多相关文章

  1. Node.js 事件循环(Event Loop)介绍

    Node.js 事件循环(Event Loop)介绍 JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”.Node.js在JavaScript的基 ...

  2. Node.js事件循环

    Node JS是单线程应用程序,但它通过事件和回调概念,支持并发. 由于Node JS每一个API是异步的,作为一个单独的线程,它使用异步函数调用,以保持并发性.Node JS使用观察者模式.Node ...

  3. Node.js 事件循环机制

    Node.js 采用事件驱动和异步 I/O 的方式,实现了一个单线程.高并发的 JavaScript 运行时环境,而单线程就意味着同一时间只能做一件事,那么 Node.js 如何通过单线程来实现高并发 ...

  4. 6、Node.js 事件循环

    #########################################################################################Node.js 事件循 ...

  5. Node.js 事件循环

    Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...

  6. Node.js 学习(五)Node.js 事件循环

    Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...

  7. [译] 所有你需要知道的关于完全理解 Node.js 事件循环及其度量

    原文地址:All you need to know to really understand the Node.js Event Loop and its Metrics 原文作者:Daniel Kh ...

  8. 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 ...

  9. 18.Node.js 事件循环

    转自:http://www.runoob.com/nodejs/nodejs-tutorial.html Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node ...

随机推荐

  1. OD20

    查壳   发现没有壳          那就载入OD 在命令中在API函数下下个断点 然后程序跑起来 ,   点CHECK  断下来了 在动态链接库断下来了      我们alt+F9   回到用户界 ...

  2. QTP对象识别tips

    1.已添加的对象无法再次识别成功,编辑对象存储库中测试对象详细信息.子对象无法识别成功时,先检查父对象是否可成功识别. 2.使用.NET Windows Forms 侦测器检测后再使用object s ...

  3. appium+Python真机运行测试demo的方法

    appium+Python真机运行测试demo的方法 一,    打开手机的USB调试模式 二,    连接手机到电脑 将手机用数据线连接到电脑,并授权USB调试模式.查看连接的效果,在cmd下运行命 ...

  4. windbg symbol path

    SRV*c:\symbols*http://msdl.microsoft.com/download/symbols;srv* DRIVER_POWER_STATE_FAILURE (9f)A driv ...

  5. SQLiteDeveloper破解

    Sqlite 管理工具 SQLiteDeveloper及破解 功能特点 表结构设计,数据维护,ddl生成,加密数据库支持,sqlite2,3支持 唯一缺憾,收费,有试用期 下载地址: http://w ...

  6. 使用 FP-growth 算法高效挖掘海量数据中的频繁项集

    前言 对于如何发现一个数据集中的频繁项集,前文讲解的经典 Apriori 算法能够做到. 然而,对于每个潜在的频繁项,它都要检索一遍数据集,这是比较低效的.在实际的大数据应用中,这么做就更不好了. 本 ...

  7. 解决msi文件在XP上安装未完成(提示安装程序被中断,未能安装app。需要重新启动该安装程序进行重试)的问题。

    如图所示,我利用Visual Studio 2015制作了一个小程序.基于.Net 4.0.用VS的Install扩展,新建Install项目进行打包.打包为.msi文件.该安装文件在已经安装了 .N ...

  8. winfrom 限制文本框小数点后两位

    private void numWeight_KeyPress(object sender, KeyPressEventArgs e) { if (char.IsNumber(e.KeyChar) | ...

  9. android 7.0变化

    一.加强版的低电耗模式 条件:1未插电源:2屏幕关闭:(N与6.0的区别就在于N在手机非静止时也可进入低电耗模式) 作用过程及方式:1充电状态:2屏幕关闭一定时间:进入一级低电限制:关闭网络,推迟CP ...

  10. linux下软件安装与升级

    待续 sudo apt-get update sudo apt-get upgrade sudo apt-get dist-upgrade