@(node,watcher)

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

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


fs.watch

  1. (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提供了另一个接口,

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

返回值同为FSWatcher,参数filename可为目录和文件,options默认为

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

  1. 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任务。

参考代码:

  1. 'use strict';
  2. var fs = require('fs');
  3. var path = require('path');
  4. var os = require('os');
  5. var watchList = {};
  6. var timer = {};
  7. var walk = function (dir, callback, filter) {
  8. fs.readdirSync(dir).forEach(function (item) {
  9. var fullname = path.join(dir, item);
  10. if (fs.statSync(fullname).isDirectory()){
  11. if (!filter(fullname)){
  12. return;
  13. }
  14. watch(fullname, callback, filter);
  15. walk(fullname, callback, filter);
  16. }
  17. });
  18. };
  19. var watch = function (name, callback, filter) {
  20. if (watchList[name]) {
  21. watchList[name].close();
  22. }
  23. watchList[name] = fs.watch(name, function (event, filename) {
  24. if (filename === null) {
  25. return;
  26. }
  27. var fullname = path.join(name, filename);
  28. var type;
  29. var fstype;
  30. if (!filter(fullname)) {
  31. return;
  32. }
  33. // 检查文件、目录是否存在
  34. if (!fs.existsSync(fullname)) {
  35. // 如果目录被删除则关闭监视器
  36. if (watchList[fullname]) {
  37. fstype = 'directory';
  38. watchList[fullname].close();
  39. delete watchList[fullname];
  40. } else {
  41. fstype = 'file';
  42. }
  43. type = 'delete';
  44. } else {
  45. // 文件
  46. if (fs.statSync(fullname).isFile()) {
  47. fstype = 'file';
  48. type = event == 'rename' ? 'create' : 'updated';
  49. // 文件夹
  50. } else if (event === 'rename') {
  51. fstype = 'directory';
  52. type = 'create';
  53. watch(fullname, callback, filter);
  54. walk(fullname, callback, filter);
  55. }
  56. }
  57. var eventData = {
  58. type: type,
  59. target: filename,
  60. parent: parent,
  61. fstype: fstype
  62. };
  63. if (/windows/i.test(os.type())) {
  64. // window 下的兼容处理
  65. clearTimeout(timer[fullname]);
  66. timer[fullname] = setTimeout(function() {
  67. callback(eventData);
  68. }, 16);
  69. } else {
  70. callback(eventData);
  71. }
  72. });
  73. };
  74. /**
  75. * @param {String} 要监听的目录
  76. * @param {Function} 文件、目录改变后的回调函数
  77. * @param {Function} 过滤器(可选)
  78. */
  79. module.exports = function (dir, callback, filter) {
  80. // 排除“.”、“_”开头或者非英文命名的目录
  81. var FILTER_RE = /[^\w\.\-$]/;
  82. filter = filter || function (name) {
  83. return !FILTER_RE.test(name);
  84. };
  85. watch(dir, callback, filter);
  86. walk(dir, callback, filter);
  87. };

node实现watcher的困境的更多相关文章

  1. Zookeeper——Watcher原理详解

    文章目录 引言 正文 一.如何注册监听 二.如何触发监听事件 三.事件类型有哪些 四.Watcher可以被无限次触发么?为什么要这么设计? 五.Watcher实现原理 1. 客服端发送请求 a. 初始 ...

  2. [转载] 跟着实例学习zookeeper 的用法

    原文: http://ifeve.com/zookeeper-curato-framework/ zookeeper 的原生客户端库过于底层, 用户为了使用 zookeeper需要编写大量的代码, 为 ...

  3. zookeeper 学习笔记 (C语言版本)

    1.zookeeper简介 zookeeper是Hadoop的子项目,在大型分布式系统中,zookeeper封装好了一些复杂易出错的服务,提供简单易用的接口,给使用者提供高效稳定的服务.这些服务包括配 ...

  4. Vue2.0源码阅读笔记--双向绑定实现原理

    上一篇 文章 了解了Vue.js的生命周期.这篇分析Observe Data过程,了解Vue.js的双向数据绑定实现原理. 一.实现双向绑定的做法 前端MVVM最令人激动的就是双向绑定机制了,实现双向 ...

  5. JavaScript 实现一个简单的MVVM前端框架(ES6语法)

    前言 随着前端各大框架的崛起,为我们平时的开发带来了相当的便利,我们不能一直停留在应用层面,今天就自己动手实现一个乞丐版的MVVM小框架 完整代码github地址 效果 html代码 <div ...

  6. CuratorFramework使用

    CuratorFrameworkFramework是ZooKeeper Client更高的抽象API 自动连接管理: 1. 当ZooKeeper客户端内部出现异常, 将自动进行重连或重试, 该过程对外 ...

  7. vue中的双向数据绑定详解

    前言 什么是数据双向绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化.这也算是vue的精髓之处了.值得注意的是,我 ...

  8. Vue 双向绑定原理

    Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统. 一.访问器属性:Object.defineProperty ECMAScript 262v5带来的新东西,FF把它归入为jav ...

  9. vue 之 双向绑定原理

    一.实现双向绑定 详细版: 前端MVVM实现双向数据绑定的做法大致有如下三种: 1.发布者-订阅者模式(backbone.js) 思路:使用自定义的data属性在HTML代码中指明绑定.所有绑定起来的 ...

随机推荐

  1. 十分钟介绍mobx与react

    原文地址:https://mobxjs.github.io/mobx/getting-started.html 写在前面:本人英语水平有限,主要是写给自己看的,若有哪位同学看到了有问题的地方,请为我指 ...

  2. 一步步开发自己的博客 .NET版(10、前端对话框和消息框的实现)

    关于前端对话框.消息框的优秀插件多不胜数.造轮子是为了更好的使用轮子,并不是说自己造的轮子肯定好.所以,这个博客系统基本上都是自己实现的,包括日志记录.响应式布局.评论功能等等一些本可以使用插件的.好 ...

  3. Android权限管理之Android 6.0运行时权限及解决办法

    前言: 今天还是围绕着最近面试的一个热门话题Android 6.0权限适配来总结学习,其实Android 6.0权限适配我们公司是在今年5月份才开始做,算是比较晚的吧,不过现在Android 6.0以 ...

  4. 一篇文章看懂TPCx-BB(大数据基准测试工具)源码

    TPCx-BB是大数据基准测试工具,它通过模拟零售商的30个应用场景,执行30个查询来衡量基于Hadoop的大数据系统的包括硬件和软件的性能.其中一些场景还用到了机器学习算法(聚类.线性回归等).为了 ...

  5. javascript之Object.defineProperty的奥妙

    直切主题 今天遇到一个这样的功能: 写一个函数,该函数传递两个参数,第一个参数为返回对象的总数据量,第二个参数为初始化对象的数据.如: var o = obj (4, {name: 'xu', age ...

  6. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  7. linux压力测试工具stress

    最近给PASS平台添加autoscaling的功能,根据服务器的负载情况autoscaling,为了测试这项功能用到了stress这个压力测试工具,这个工具相当好用了.具体安装方式就不说了.记录下这个 ...

  8. 万向节锁(Gimbal Lock)的理解

    [TOC] 结论 我直接抛出结论: Gimbal Lock 产生的原因不是欧拉角也不是旋转顺序,而是我們的思维方式和程序的执行逻辑没有对应,也就是说是我们的观念导致这个情况的发生. 他人解释 首先我们 ...

  9. ubuntu14 安装及卸载vmware

    原帖http://blog.sina.com.cn/s/blog_73dac6b50101gp4f.html 适用于ubuntu14和vmware player 12.5

  10. mono for android Json 上传文件

    void button_Click(object sender, EventArgs e) { string Url = "上传地址,服务器端负责接收"; byte[] fbyte ...