节点 - 如何等待异步操作?

时间:2013-08-26 19:58:02

标签: node.js asynchronous mocha

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

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

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包围断言,则断言传递。

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

我不认为正确的答案是同步读取文件,因为这会阻止节点请求/响应循环,对吗?

奖金问题:
即使我将断言置于具有0超时值的setTimeout中,测试仍会通过。这是因为只是将它放在一个setTimeout中就可以将它踢到处理链的末尾或者其他什么来使文件系统工作首先完成?

3 个答案:

答案 0 :(得分: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);
    });
}

编辑:由于您只需要调用一次,因此可以在应用程序加载时将其初始化一次。根据{{​​3}},模块在第一次加载后被缓存。上面两个例子也已经编辑过了。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

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-promissed允许您使用eventually直接使用promises,或者您可以使用传递给您的测试的回调我认为:

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);
            })
        });
    });
});