node实现watcher的困境
2016-01-06 17:23
483 查看
@(node,watcher)
watcher,在如今的前端领域已经数见不鲜了。目前流行的gulp流程工具提供了watcher的选项,是我们在开发过程中不需要手动进行触发构建流程,转而根据文件(目录)内容改变来触发。
深入到watcher实现层,其实是基于node的fs.watch API,但是fs.watch有很多“不确定性”,下文会一一解答。
[TOC]
watch API很简单,接受三个参数,并返回一个FSWatcher对象。
filename可以是文件,也可是目录;
options为可选对象,默认为
listener为回调函数,接受两个参数,分别为event和filename,其中事件有两种类型,“rename”和“change”,而filename也有兼容性问题,在使用时也要注意兼容性判断。
recursive属性在linux下失效;
watch目录时,回调函数中的filename只在linux和windows下可以获取;
node在任何情况下都不确保filename可以获取到
返回值同为FSWatcher,参数filename可为目录和文件,options默认为
{ persistent: true, interval: 5007 },其中interval则为node轮训该文件的时间间隔,listener接受两个参数,即类行为fs.Stat的curr和prev对象,我们可通过
判断文件是否发生改动。
不管在何种系统设计中,轮训的方式都是兼容性保底方案,只要我们的系统支持fs.watch方法,就不用采用该种方式进行兼容。
那么合适可以采用轮训呢?我认为,大概分两种情况:
需要针对文件的元信息判断是否触发事件
监控的文件所在的操作系统,如果是NFS, SMB等网络文件系统,fs.watch并不提供功能,因此只能使用轮训方式(watch方法是基于文件系统的特性编写的,在linux下基于“inotify”,windows下基于“ReadDirectoryChangesW”)
采用默认的options配置,即
针对单个文件做watch,OSX可以获取到filename
通过简单的处理,一个简易的watcher就实现了,配合着EventEmit,就可以通过事件的方式完成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); };
相关文章推荐
- Nodejs 学习01:如何写一个模块
- Express+Socket.IO 搭建即时聊天
- node测试相关工具
- 《nodejs开发指南》微博实例express4.x版
- 简单总结iNode和block知识
- nodejs 设置proxy
- nodejs+mongoose简单关联+增删改查
- 关于Node的async包的一点笔记
- chat聊天室的架设(nodejs)
- nodejs安装supervisor
- Express+Socket.io 404,400解决方案
- nodejs中的underscore.js
- Lintcode: Route Between Two Nodes in Graph
- Ext2文件系统 inode
- 关于nodeType的相关问题
- nodejs API
- 使用hexo生成博客
- nodejs review-03
- NodeJS中 package.json 解析
- 【LEETCODE】24-Swap Nodes in Pairs