为什么这个 fs.readFile 循环没有将其结果推送到我的数组?

Why is this fs.readFile loop not pushing its results to my array?

#!/usr/bin/env node

var fs = require('fs')
  , async = require('async') 
  , program = require('commander')

program
  .version('0.0.1')
  .usage('<keywords>')
  .parse(process.argv)

async.waterfall([
  fs.readdir.bind(fs, __dirname),
  parseHTML,
], saveResult)

function parseHTML(files, callback) {
  var result = []

  files.forEach(function(file) {
    if (file.match(/\.html$/)) {
      fs.readFile(file, 'utf8', function(err, data) {
        if (err) throw err
        result.push(data)
      })
    }
  })

  callback(null, result)
}

function saveResult(err, result) {
  console.log(result)
}

我很困惑,因为 console.log(data) 确实输出了数据:

<p>File 1</p>

<p>File 2</p>

然而最后的 result 是一个空数组:[]

为什么我做错了?

您必须等到最后一个 fs.readFile() 操作完成后才能查看结果。这些是异步操作,它们会在未来的某个时间完成。在他们中的任何一个完成之前,您正在检查结果。

有很多方法可以解决这个问题,但这种方法可能对您的代码造成的更改最少,因为它只是记录已完成的数量:

function parseHTML(files, callback) {
    var result = [],
        cntr = 0;

    files.forEach(function(file) {
        if (file.match(/\.html$/)) {
            fs.readFile(file, 'utf8', function(err, data) {
                if (err) throw err
                result.push(data)
                    // see if we're done processing all the results
                    ++cntr;
                if (cntr === files.length) {
                    callback(null, result);
                }
            });
        } else {
            ++cntr;
            if (cntr === files.length) {
                callback(null, result);
            }
        }
    });
}

我个人更喜欢使用 promises 和 Promise.all() 来解决这个问题。

这是一个使用 Bluebird promise 库的版本,它保留了您的一些其他结构:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require('fs'));

// your other code here

function parseHTML(files, callback) {
    var promises = [];

    files.forEach(function(file) {
        if (file.match(/\.html$/)) {
            promises.push(fs.readFileAsync(file, 'utf8'));
    });
    Promise.all(promises).then(function(results) {
        // all results in results array
        callback(null, results);
    }, function(err) {
       // error here
    });
}

而且,这是一个完全承诺的版本:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require('fs'));

function parseHTML(files) {
    var promises = [];

    files.forEach(function(file) {
        if (file.match(/\.html$/)) {
            promises.push(fs.readFileAsync(file, 'utf8'));
    });
    return Promise.all(promises);
}

fs.readdirAsync(__dirname).then(parseHTML).then(function(results) {
    // files are in the results array here
}).catch(function(err) {
    // error here
});