【问题标题】:Is it okay to use synchronous code to load data into the memory at startup?在启动时使用同步代码将数据加载到内存中可以吗?
【发布时间】:2015-07-12 11:13:23
【问题描述】:

在我的routes.js 文件中,我有这个:

var pages = require('./pages')();
...
app.get('/:page', function(req, res, next) {
    var p = req.params.page;
    if (p in pages) {
        res.render('page', pages[p]);
    } else {
        next();
    }
});

pages.js:

module.exports = function() {
    var fs = require('fs'),
        ret = [],
        dir = './pages',
        files = fs.readdirSync(dir);
    files.forEach(function(file) {
        var text = fs.readFileSync(dir + '/' + file, 'utf-8'),
            fileName = file.substr(0, file.lastIndexOf('.'));
        ret[fileName] = {content: text};
    });
    return ret;
};

当我运行 node.js 时,此代码只运行一个。这就是我可以使它异步的方法:

require('./pages')(function(pages) {
    app.get('/:page', function(req, res, next) {
        var p = req.params.page;
        if (p in pages) {
            res.render('page', pages[p]);
        } else {
            next();
        }
    });
});

pages.js:

module.exports = function(callback) {
    var fs = require('fs'),
        ret = [],
        dir = './pages';
    fs.readdir(dir, function(err, files) {
        if (err) throw err;
        files.forEach(function(file, i) {
            fs.readFile(dir + '/' + file, 'utf-8', function(err, text) {
                if (err) throw err;
                var fileName = file.substr(0, file.lastIndexOf('.'));
                ret[fileName] = {content: text};
                if ( i === (files.length - 1) ) callback(ret);
            });
        });
    });
};

假设总页面大小不超过 1 MB,我可以无限期地将文本缓存到内存中,而不会因内存不足而导致节点崩溃。

  1. 我应该使用异步代码吗?

    根据我了解到的情况,异步版本将使节点更快地开始监听本地主机,但/:page URL 仅在文件加载到内存时才有效。

  2. 异步代码的模式是否正确?

  3. 如果我需要在另一个文件中重用pages 对象怎么办?目前只能通过routes.js访问。

我可以像这样重写pages.js 只执行一次吗:

var ret = [];
module.exports = function(callback) {
    var fs = require('fs'),
        dir = './pages';
    if (ret.length < 1) {
        fs.readdir(dir, function(err, files) {
            if (err) throw err;
            files.forEach(function(file) {
                fs.readFile(dir + '/' + file, 'utf-8', function(err, text) {
                    if (err) throw err;
                    var fileName = file.substr(0, file.lastIndexOf('.'));
                    ret[fileName] = {content: text};
                    if ( i === (files.length - 1) ) callback(ret);
                });
            });
        });    
    } else {
        callback(ret);
    }
};
  1. 如果require('./pages')(function(pages) {}) 被多次调用会怎样? if 条件是否有可能失败?我无法解决这个问题。

【问题讨论】:

  • 是的,同步加载 (require) 是 node.js 的做法,而且人们似乎仍在使用 node.js... 使用 async 并没有很大的回报加载,因为在满足依赖关系之前应用程序将无法使用。一旦你开始,同步可能会减慢一堆请求,但在所有内容加载之前什么都不会发生,所以使用同步保持快速和简单。
  • 还考虑到您是从 HDD 加载,而不是 HTTP,因此异步并不像在浏览器中那样重要,因为同步 ajax 会冻结选项卡并可能需要一段时间......

标签: javascript node.js asynchronous express promise


【解决方案1】:

我应该使用异步代码吗?

如果你愿意,为什么不呢。但实际上并没有必要,启动时同步 IO 就可以了。 require 也可以。

异步代码的模式是否正确?

没有。它确实为每个目录调用一次callback。多次调用app.get('/:page', …) 不是你想要的。

如果我需要在另一个文件中重用 pages 对象怎么办?目前它只能在 routes.js 中访问。

您可以将它从routes.js 传递给其他模块。或者只是重写pages.js 来静态存储它并且只执行一次异步的东西,这样你就可以多次要求它。

如果require('./pages')(function(pages) {}) 被多次调用会怎样? if 条件是否有可能失败?

是的,它肯定会失败,因为您只是异步填充 ret

我无法完全解决这个问题。

使用承诺。充当异步的、不可变的值,正是您需要的。他们将保证回调只被调用一次,每个回调都使用相同的ret 值调用,并提供更多有用的东西(比如为您管理并行文件读取)。

您需要从pages.js 导出一个承诺。

【讨论】:

  • 非常感谢您的回答。我完全错过了异步代码中的回调处于循环中的事实。所以我可以添加一个 if 条件来确保回调仅在循环的最后一次迭代中被调用,这也将修复异步代码,对吧?
  • 是的,但它非常复杂,并且仍然存在多次调用的问题。只需使用同步代码或承诺。
  • 非常感谢,非常感谢您的帮助。我的理解是if ( i === (files.length - 1) ) callback(ret); 可以完成这项工作(不包括多次调用问题),但readFile 是异步的,因此可以在调用其他回调之前执行最后一个回调。
  • 是的,您不能使用闭包中的iteration 变量,您必须使用每次调用都会增加的计数器。但实际上,您不想手动执行此操作,请使用 Promise (Promise.all) 或 async.js (async.parallel) 之类的辅助库。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-08
  • 1970-01-01
  • 1970-01-01
  • 2017-08-14
  • 2016-02-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多