Node.js:回调变量作用域的问题

Node.js: problem with variable scope in callback

本文关键字:作用域 问题 变量 回调 js Node      更新时间:2023-09-26
var result = { controllers: [], views: [], models: [] };
var dirs = ['controllers', 'views', 'models'];
dirs.forEach(function(dirname) {
    fs.readdir('./' + dirname, function(err, res) {
        if (err) throw err;
        result[dirname] = res;
        // #2
    });
});
// #1

在这段代码中,让console.log(result);运行在#1(见上文),空的控制器、视图和模型数组将被初始化记录。但是,我需要循环用通过fs读取的相应文件名填充数组。

#2的console.log(result);将在第三次迭代后记录填充所需值的result对象。

我相信这与Node.js/JavaScript回调的异步特性有关。如果我不理解JavaScript变量作用域和异步方法是如何工作的,请原谅我,我都是新手。

这样做:

var result = { controllers: [], views: [], models: [] };
var dirs = ['controllers', 'views', 'models'];
var pending = 0;
dirs.forEach(function(dirname) {
    pending++;
    fs.readdir('./' + dirname, function(err, res) {
        pending--;
        if (err) throw err;
        result[dirname] = res;
        if (pending===0) goOn();
    });
});
function goOn() {
    // #1
}

我相信这与的异步特性有关Node.js/JavaScript回调。

是的,这可能就是为什么当您尝试在#1处输出result变量的内容时,它是空的原因。在#1运行的时候,数据还没有被获取,因为在#2执行readdir的回调时发生了"获取"动作。为了更好地了解回调和异步编程的工作原理,我建议您查看本文中所述的一些关于异步范式的参考资料。

对于那些对措辞感到困惑的人来说,下面是一个示例的输出,每个目录中都有一个'1'文件:

~/Documents/$ node test.js 
{ controllers: [], views: [], models: [] } 1
{ controllers: [], views: [ '1' ], models: [] } 2
{ controllers: [ '1' ], views: [ '1' ], models: [] } 2
{ controllers: [ '1' ],
  views: [ '1' ],
  models: [ '1' ] } 2

这是因为进入文件系统需要异步操作。因此,由于Node不阻塞,之后的值将与异步操作完成之前的值相同。

它确实与回调有关。在

fs.readdir('./' + dirname, function(err, res) {
    if (err) throw err;
    result[dirname] = res;
    // #2
});

传入的函数是一个回调函数,当目录被完全读取时执行。由于它是异步的,fs.readdir()在函数实际执行之前返回

所以基本上,在forEach完成之后,有3个函数等待作为回调函数执行(每个目录一个)。代码在继续执行之前不会等待回调发生,因此如果在读取目录之前到达#1,它将在回调执行之前记录结果对象并对其进行适当修改。

可以使用fs。readdirSync,但只有当它不会对你的应用程序不好,如果这段代码短暂阻塞/没有危险,这段代码无限期阻塞和拖延你的程序。如果您需要它保持异步,请查看jh的答案。

我认为这与dirname在第一个函数返回后改变的值有关。要解决这个问题,只需将dirname复制到第二个函数调用的作用域中;

dirs.forEach(function(dirn) {
    var dirname = dirn;  // make a copy here
    fs.readdir('./' + dirname, function(err, res) {
        if (err) throw err;
        result[dirname] = res;
        // #2
    });
});