node实现watcher的困境
@(node,watcher)
watcher,在如今的前端领域已经数见不鲜了。目前流行的gulp流程工具提供了watcher的选项,是我们在开发过程中不需要手动进行触发构建流程,转而根据文件(目录)内容改变来触发。
深入到watcher实现层,其实是基于node的fs.watch API,但是fs.watch有很多“不确定性”,下文会一一解答。
[TOC]
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);
};
- Python进阶07 函数对象
- Python进阶06 循环对象
- Python进阶05 循环设计
- 剑指OFFER之复杂链表的复制(九度OJ1524)
- Python进阶04 函数的参数对应
- Python进阶03 模块
- Python进阶02 文本文件的输入输出
- Python进阶01 词典
- 微信公布上线小程序游戏 腾讯应用宝安卓平台首发
- Python基础10 反过头来看看
- CCCF 微软沈向洋:理解自然语言:表述、对话和意境
- Linux进程基础
- Android Studio导入项目非常慢的解决办法
- Android开发中遇到的requestFeature() must be called before adding content异常
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Flutter 实现进度条效果
- Android 侧边滑动关闭Activity的示例代码
- Flutter 系统是如何实现ExpansionPanelList的示例代码
- Flutter中如何使用WillPopScope的示例代码
- Android实现音乐播放进度条传递信息的两种方式(在service和activity中)
- Flutter 中 Dart的Mixin示例详解
- Android Studio 实现九宫格功能
- Android user版通过adb_enable开启adb 调试 不提示对话框的流程分析
- Android实现圆线按钮进度效果
- Flutter 透明状态栏及字体颜色的设置方法
- Android实现带有进度条的按钮效果
- Android 自定义View实现多节点进度条功能
- android studio无法添加 bmob sdk依赖问题及解决方法
- Android自定义ToolBar并实现沉浸式的方法
- python初学者笔记—入门基础知识