zoukankan      html  css  js  c++  java
  • node实现watcher的困境

    @(node,watcher)

    watcher,在如今的前端领域已经数见不鲜了。目前流行的gulp流程工具提供了watcher的选项,是我们在开发过程中不需要手动进行触发构建流程,转而根据文件(目录)内容改变来触发。

    深入到watcher实现层,其实是基于node的fs.watch API,但是fs.watch有很多“不确定性”,下文会一一解答。


    fs.watch

    	(fs.FSWatcher) fs.watch(filename[, options][, listener])
    

    watch API很简单,接受三个参数,并返回一个FSWatcher对象。
    filename可以是文件,也可是目录;
    options为可选对象,默认为 { persistent: true, recursive: false },其中persistent属性意味着:watcher进程会一直watch该文件(目录),即watcher进程阻塞;recursive属性意味着:如果监听的是目录,则目录下属的目录和文件也会被监听,recursive属性存在兼容性问题,在linux系统下无效,在windows和OSX下正常。
    listener为回调函数,接受两个参数,分别为event和filename,其中事件有两种类型,“rename”和“change”,而filename也有兼容性问题,在使用时也要注意兼容性判断。

    问题

    在上一节中简单介绍了watch API,也简单提到了一些兼容性问题,在此列举出来:

    • recursive属性在linux下失效;
    • watch目录时,回调函数中的filename只在linux和windows下可以获取;
    • node在任何情况下都不确保filename可以获取到

    解决方案

    轮训

    node提供了另一个接口,

    	fs.watchFile(filename[, options], listener)
    

    返回值同为FSWatcher,参数filename可为目录和文件,options默认为
    { persistent: true, interval: 5007 },其中interval则为node轮训该文件的时间间隔,listener接受两个参数,即类行为fs.Stat的curr和prev对象,我们可通过

    	curr.mtime == prev.mtime
    

    判断文件是否发生改动。

    不管在何种系统设计中,轮训的方式都是兼容性保底方案,只要我们的系统支持fs.watch方法,就不用采用该种方式进行兼容。

    那么合适可以采用轮训呢?我认为,大概分两种情况:

    • 需要针对文件的元信息判断是否触发事件
    • 监控的文件所在的操作系统,如果是NFS, SMB等网络文件系统,fs.watch并不提供功能,因此只能使用轮训方式(watch方法是基于文件系统的特性编写的,在linux下基于“inotify”,windows下基于“ReadDirectoryChangesW”)

    手动适配

    针对非网络文件系统,watch API的兼容性就在于是否递归watch以及OSX下filename获取的问题,因此我们可以通过编码方式解决:

    • 采用默认的options配置,即{ persistent: true, recursive: false },通过walker便利目录,针对单个文件作watcher
    • 针对单个文件做watch,OSX可以获取到filename

    通过简单的处理,一个简易的watcher就实现了,配合着EventEmit,就可以通过事件的方式完成watcher任务。

    参考代码:

    'use strict';
    
    var fs = require('fs');
    var path = require('path');
    var os = require('os');
    
    
    var watchList = {};
    var timer = {};
    
    
    var walk = function (dir, callback, filter) {
        fs.readdirSync(dir).forEach(function (item) {
            var fullname = path.join(dir, item);
    
            if (fs.statSync(fullname).isDirectory()){
    
                if (!filter(fullname)){
                    return;
                }
    
                watch(fullname, callback, filter);
                walk(fullname, callback, filter);
            }
        });
    };
    
    
    var watch = function (name, callback, filter) {
    
        if (watchList[name]) {
            watchList[name].close();
        }
    
        watchList[name] = fs.watch(name, function (event, filename) {
    
            if (filename === null) {
                return;
            }
    
            var fullname = path.join(name, filename);
            var type;
            var fstype;
    
            if (!filter(fullname)) {
                return;
            }
    
            // 检查文件、目录是否存在
            if (!fs.existsSync(fullname)) {
    
                // 如果目录被删除则关闭监视器
                if (watchList[fullname]) {
                    fstype = 'directory';
                    watchList[fullname].close();
                    delete watchList[fullname];
                } else {
                    fstype = 'file';
                }
    
                type = 'delete';
    
            } else {
    
                // 文件
                if (fs.statSync(fullname).isFile()) {
    
                    fstype = 'file';
                    type = event == 'rename' ? 'create' : 'updated';
    
                // 文件夹
                } else if (event === 'rename') {
    
                    fstype = 'directory';
                    type = 'create';
    
                    watch(fullname, callback, filter);
                    walk(fullname, callback, filter);
                }
    
            }
    
            var eventData = {
                type: type,
                target: filename,
                parent: parent,
                fstype: fstype
            };
    
    
            if (/windows/i.test(os.type())) {
                // window 下的兼容处理
                clearTimeout(timer[fullname]);
                timer[fullname] = setTimeout(function() {
                    callback(eventData);
                }, 16);
    
            } else {
                callback(eventData);
            }
    
    
        });
    
    };
    
    
    /**
     * @param   {String}    要监听的目录
     * @param   {Function}  文件、目录改变后的回调函数
     * @param   {Function}  过滤器(可选)
     */
    module.exports = function (dir, callback, filter) {
    
        // 排除“.”、“_”开头或者非英文命名的目录
        var FILTER_RE = /[^w.-$]/;
        filter = filter || function (name) {
            return !FILTER_RE.test(name);
        };
    
        watch(dir, callback, filter);
        walk(dir, callback, filter);
    };
    
  • 相关阅读:
    SDK Manager.exe和AVD Manager.exe缺失,Android SDK Tools在检查java环境时卡住了,未响应卡死!
    GetLastError结果列表
    VS2008 远程调试器未成功安装,没法使用?
    远程桌面连接无法相互拷贝文件了?
    VS2008编译错误:error C2065: 'PMIB_TCPSTATS' : undeclared identifier c:program files (x86)microsoft sdkswindowsv7.0aincludeiphlpapi.h 411
    C++ 保存Excel文件(带密码保护)
    UTF8与std:string互转
    将windbg设置为默认调试器命令
    VC++ 链接错误LINK : fatal error LNK1104: cannot open file "*.lib"
    IAP升级程序中Bootloader和APP程序中断复用的解决办法
  • 原文地址:https://www.cnblogs.com/accordion/p/5106364.html
Copyright © 2011-2022 走看看