遭遇回调函数产生的陷阱
发布于 13 年前 作者 sumory 9206 次浏览 最后一次编辑是 8 年前

在监控系统中要做一个监控文件的功能:实时监控日志文件,若发生变化则使用websocket通知前端(web页面),并返回新增的日志记录。

出事代码是这样的:

/**
 * 文件监控处理
 * 
 * [@param](/user/param) socket
 */
function monitor_handler(socket){
    var files_to_monitor =config.files_to_monitor;
    for(var key in files_to_monitor){
        console.log('开始:'+key);
        fs.watchFile(files_to_monitor[key].file_path ,  function(curr, prev){
            console.log('触发:'+key + ',处理文件'+files_to_monitor[key].file_path);
            if (curr.mtime.getTime() !== prev.mtime.getTime()) {
                notify_client(key, files_to_monitor[key].file_path, curr, socket);
            }
        });
    } 
}

乍看上去,代码很简单,config.files_to_monitor的结构是这样的:

files_to_monitor : {
        'data' : {
            label : '日志文件1',
            file_path : '/home/tmp2/data',
        },
        'data2' : {
            label : '日志文件2',
            file_path : '/home/tmp2/data2',
        },
        'data3' : {
            label : '日志文件3',
            file_path : '/home/tmp2/data3',
        },
    }

也就是需要监控配置的每个文件,然后在它发生变化后通过指定的key来emit前端websocket事件。所以fs.watchFile的回调会调用这个key,如果说到这里你能明白的话,那就不用往下看了。

按照上述代码,测试时,我分别改变三个文件,然后观察websocket的debug信息,结果是这样的:

root[@sumory-laptop](/user/sumory-laptop):/home/tmp2# echo data '模拟数据' > data
root[@sumory-laptop](/user/sumory-laptop):/home/tmp2# echo data '模拟数据' > data2
root[@sumory-laptop](/user/sumory-laptop):/home/tmp2# echo data '模拟数据' > data3

触发:data3,处理文件/home/tmp2/data3
   debug - websocket writing 5:::{"name":"data3","args":[{"updatetime":"2012-3-25 19:12:04","content":"data fdsffsfdsfsfdfsfdsfsdfdsfsfds\n"}]}
触发:data3,处理文件/home/tmp2/data3
   debug - websocket writing 5:::{"name":"data3","args":[{"updatetime":"2012-3-25 19:12:07","content":"data fdsffsfdsfsfdfsfdsfsdfdsfsfds\n"}]}
触发:data3,处理文件/home/tmp2/data3
   debug - websocket writing 5:::{"name":"data3","args":[{"updatetime":"2012-3-25 19:12:10","content":"data 模拟数据\n"}]}

好吧,2B了,永远只会触发data3:

  • 起初还以为代码有问题,挨个字母检查,没问题啊;
  • 仔细查看nodejs的fs部分文档,函数也没有用错;
  • 增加一配置项,发现只会调用最后一项配置,也就是for循环的最后一项。

恍然大悟,fs.watchFile监控并触发事件不是通过回调的嘛,回调在声明时不会调用,只有文件发生变化是才会调用,这是的key不是已经循环到最后了,当然也就会永远只触发最后一个(好吧,就这么一个坑,我就粪不顾身地随便跳进去了…)。

改写后正确的结果:

/**
 * 文件监控处理
 * 
 * [@param](/user/param) socket
 */
function monitor_handler(socket){
    var files_to_monitor =config.files_to_monitor;
    for(var key in files_to_monitor){
        console.log('开始:'+key);
        watch_file(key, files_to_monitor[key].file_path, socket);
    } 
}

function watch_file(key, full_file_path, socket){
    fs.watchFile(full_file_path,  function(curr, prev){
        console.log('触发:'+key + ',处理文件'+full_file_path);
        if (curr.mtime.getTime() !== prev.mtime.getTime()) {
            notify_client(key, full_file_path, curr, socket);
        }
    });
}

恶心得蛋都碎了…以后还是留点神吧…

17 回复

收藏了! :)

楼主前端js写得不多吧?~.~ 看看 这里

刚学不到2个月

@sumory 以前循环添加事件也掉过这个坑, 想不通还这么玄乎弄得
http://www.zhihu.com/question/20019257
http://www.laruence.com/2009/05/28/863.html

for(var key in files_to_monitor){ console.log(‘开始:’+key); fs.watchFile(files_to_monitor[key].file_path , foobar.bind(null, key)); }

function foobar(key, curr, prev){
    console.log('触发:'+key + ',处理文件'+files_to_monitor[key].file_path);
    if (curr.mtime.getTime() !== prev.mtime.getTime()) {
        notify_client(key, files_to_monitor[key].file_path, curr, socket);
    }
}

改成这样好点吧,不然每次watch_file运行都会创建一个函数对象

这样能工作?fs.watchFile(filestomonitor[key].file_path , foobar.bind(null, key)); 这里面都没有传入curr和prev啊

@sumory 看這個 https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind 不過.bind應該也會生成一個func… 所以內存消耗貌似一樣,我傻逼了

哈哈,这个坑是js里常见的坑了,我以前也掉进过@.@

一不小心又是一个BUG.

这个问题我之前也遇到过, 在回调函数里面引用了局外的变量.

这么写不够优雅,不知道能不能从参数里面获取你要的信息而不直接引用局外的变量?

参看下这个,让它立即执行把参数传进去

var numbers= [];  

for (var i = 0; i < 10; i++) {  
    numbers[i] = (function(tmp) {  
        return function() {  
            return tmp;  
        }  
    }(i)); 
}  
  
for (var j = 0; j < 10; j++) {  
    print(numbers[j]()); //返还0,1,2.....9  
}

哈哈,闭包吧,,

不要用循環

for(var key in files_to_monitor)

files_to_monitor.forEach

就沒有這個問題了。

不能发布话题,故在此贴求问一件事,还盼有心人看见给予答复。问题是 现在node.js是否有现成的文本压缩和解压算法,如lzw,lz77等的实现?

哥们,你太有才了…

好像终于懂了, 因为 JS 的循环没有块级作用域所以变成这样了啊…

/**
  • 文件监控处理
  • @param socket */ function monitor_handler(socket){ var files_to_monitor =config.files_to_monitor; for(var key in files_to_monitor){ (function(){ console.log(‘开始:’+key); fs.watchFile(files_to_monitor[key].file_path , function(curr, prev){ console.log(‘触发:’+key + ‘,处理文件’+files_to_monitor[key].file_path); if (curr.mtime.getTime() !== prev.mtime.getTime()) { notify_client(key, files_to_monitor[key].file_path, curr, socket); } }); })() } } 这样就行了
回到顶部