1. 程式人生 > >Node.js的循環與異步問題

Node.js的循環與異步問題

文件的 call 讓我 cti fin 輸出結果 退出 -i ack

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. for (var i = 0; i < files.length; i++) {
  5.   (function (i) {
  6.   fs.readFile(files[i], ‘utf-8‘, function (err, contents) {
  7.   console.log(files[i] + ‘: ‘ + contents);
  8. });
  9. })(i);
  10. }

上面代碼在 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的循環與異步問題