Node.js的循环与异步问题

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

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.txtb.txt c.txt ,并输出文件名和内容。假设这三个文件的内容分别是 AAA BBB CCC,那么咱们指望的输出结果就是: 数组

  a.txt: AAA 闭包

  b.txt: BBB 异步

  c.txt: CCC 函数式编程

但是咱们运行这段代码的结果是怎样的呢?居然是这样的结果: 函数

  undefined: AAA oop

  undefined: BBB post

  undefined: CCC ui

这个结果说明文件内容正确输出了,而文件名却不对,也就意味着,contents 的结果是正确的,但 files[i] 的值是 undefined。这怎么可能呢,文件名不正确却能读取文件内容?既然难以直观地理解,咱们就把 files[i] 分解并打印出来看看,在读取文件的回调函数中分别输出 filesi 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. });
相关文章
相关标签/搜索