【问题标题】:Node - how to wait on async operations?节点 - 如何等待异步操作?
【发布时间】:2023-03-27 11:27:01
【问题描述】:

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

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

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 中就会将它踢到处理链的末尾还是什么所以文件系统工作首先完成?

【问题讨论】:

    标签: node.js asynchronous mocha.js


    【解决方案1】:

    我认为这是一个很好的测试,在任何使用您的模块的应用程序中都会发生同样的事情,即它的代码可以在设置 files 之前运行。你需要做的是像@making3 建议的那样创建一个回调,或者使用promises。我没用过mocha,但是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 直接处理 Promise,或者您可以使用传递给您的测试的回调,我认为:

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

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

        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 documentation,模块在第一次加载后被缓存。上面的两个例子也做了修改。

        【讨论】:

        • 嗯...好吧,我明白你的意思了。我希望我会有一些模块来加载文件,然后我可以只需要('fileLoader')或其他任何东西,这样我就可以把文件还给我。我必须在很多地方使用它,并且到处使用回调似乎很尴尬。但是,是的,它会起作用。
        • @RationalGeek 这就是 node.js 的处理方式。一开始觉得很别扭,但不要抗拒。
        • @RationalGeek,我明白你的意思。我已经用替代解决方案更新了答案。我不会同步执行此操作,但我同意 JohnnyHK 的观点,即这样做最终会感觉更好。
        • 谢谢大家。即使在应用程序启动时初始化对我来说似乎也很尴尬,因为其他模块将无法依赖初始化完成,直到未来不确定的时间。我会更多地考虑这个......
        猜你喜欢
        • 2016-12-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-31
        • 1970-01-01
        相关资源
        最近更新 更多