node实现watcher的困境
目錄
- fs.watch
- 問題
- 解決方案
- 輪訓
- 手動適配
@(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對象,我們可通過
判斷文件是否發生改動。
不管在何種系統設計中,輪訓的方式都是兼容性保底方案,只要我們的系統支持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); };轉載于:https://www.cnblogs.com/accordion/p/5106364.html
總結
以上是生活随笔為你收集整理的node实现watcher的困境的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LOLs11吸血鬼出什么装备 出装顺序是
- 下一篇: YTU 1009: University