0

抱歉,刚从节点开始。这可能是一个非常新手的问题。

假设我有一些代码从文件系统的目录中读取一些文件:

var fs = require('fs');

fs.readdir(__dirname + '/myfiles', function (err, files) {
    if (err) throw err;
    files.forEach(function (fileName) {
        fs.readFile(__dirname + '/myfiles/' + fileName, function (err, data) {
            if (err) throw err;
            console.log('finished reading file ' + fileName + ': ' + data);
            module.exports.files.push(data);
        });
    });
});

请注意,所有这些都是异步发生的。假设我有一个执行此代码的 Mocha 测试:

describe('fileProvider', function () {
    describe('#files', function () {
        it.only('files array not empty', function () {
            assert(fileProvider.files.length > 0, 'files.length is zero');
        });
    });
});

mocha 测试在文件读取完成之前运行。我知道这一点是因为在看到表示正在运行 mocha 测试的小点之后,我看到了 console.log 语句(至少我认为这是所指示的)。此外,如果我用 setTimeout 包围断言,则断言通过。

我应该如何构建我的代码以确保完成异步文件操作?请注意,这不仅仅是测试的问题 - 我需要完全加载文件,然后才能在我的应用程序中进行实际工作。

我不认为正确的答案是同步读取文件,因为那会阻塞 Node 请求/响应循环,对吧?

奖励问题:
即使我将断言放入超时值为 0 的 setTimeout 中,测试仍然通过。这是因为仅仅将它放在 setTimeout 中就会将它踢到处理链的末尾还是什么东西让文件系统工作首先完成?

4

3 回答 3

2

您可以在读取所有文件后实现完整的回调。

exports.files = [];
exports.initialize = initialize;

function initialize(callback) {
    var fs = require('fs');

    fs.readdir(__dirname + '/myfiles', function (err, files) {
        if (err) throw err;
        files.forEach(function (fileName) {
            fs.readFile(__dirname + '/myfiles/' + fileName, function (err, data) {
                if (err) throw err;
                console.log('finished reading file ' + fileName + ': ' + data);
                exports.files.push(data);
                if (exports.files.length == files.length) {
                    callback();
                }
            });
        });
}

您可以通过执行以下操作来调用文件操作方法:

var f = require('./files.js');

if (f.files.length < 1) {
    console.log('initializing');
    f.initialize(function () { 
        console.log('After: ' + f.files.length);

        var another = require('./files.js');
        console.log('Another module: ' + another.files.length);
    });
}

编辑:由于您只想调用一次,因此可以在应用程序加载时对其进行一次初始化。根据Node.js 文档,模块在第一次加载后会被缓存。上述两个示例也已被编辑。

于 2013-08-26T20:05:50.903 回答
1

避免陷入嵌套回调。您可能希望使用async 的each 允许您以非阻塞方式异步执行任务:

https://github.com/caolan/async#each

于 2016-03-11T10:13:18.393 回答
0

我认为这是一个很好的测试,在任何使用您的模块的应用程序中都会发生同样的事情,即它的代码可以在files设置之前运行。你需要做的是创建一个像@making3 建议的回调,或者使用promise。我没用过 mocha,但是有一节是关于异步调用的。您可以导出承诺本身:

module.exports.getFiles = new Promise((resolve, reject) => {
  datas = [];
  fs.readdir(__dirname + '/myfiles', function (err, files) {
      if (err) {
        reject(err);
        return;
      }
      files.forEach(function (fileName) {
          fs.readFile(__dirname + '/myfiles/' + fileName, function (err, data) {
              if (err) {
                reject(err);
                return;
              }
              console.log('finished reading file ' + fileName + ': ' + data);
              datas.push(data);
              if (datas.length == files.length) {
                resolve(datas);
              }
          });
      });
  });
}

chai-as-promised让您可以使用 直接使用承诺eventually,或者您可以使用传递给您的测试的回调,我认为:

describe('fileProvider', function () {
    describe('#files', function () {
        it.only('files array not empty', function (done) {
            fileProvider.getFiles.then(function(value) {
                assert(value.length > 0, 'files.length is zero');
                done();
            }, function(err) {
                done(err);
            })
        });
    });
});
于 2016-08-25T10:50:23.357 回答