diff --git a/README.md b/README.md index 2f1b50c..3cf2ac1 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,8 @@ var gaze = new Gaze(pattern, options, callback); * `options` The options object passed in. * `interval` {integer} Interval to pass to fs.watchFile - * `debounceDelay` {integer} Delay for events called in succession for the same - file/event in milliseconds + * `debounceDelay` {integer} Delay for events called in succession for the same file/event in milliseconds. Default is `500`. + * `debounceImmediate` {boolean} Whether events should be emitted on the leading edge of the wait interval. Default is `true`. * `mode` {string} Force the watch mode. Either `'auto'` (default), `'watch'` (force native events), or `'poll'` (force stat polling). * `cwd` {string} The current working directory to base file patterns from. Default is `process.cwd()`. diff --git a/lib/gaze.js b/lib/gaze.js index 1f00567..b436775 100644 --- a/lib/gaze.js +++ b/lib/gaze.js @@ -41,6 +41,7 @@ function Gaze (patterns, opts, done) { opts.mark = true; opts.interval = opts.interval || 100; opts.debounceDelay = opts.debounceDelay || 500; + opts.debounceImmediate = opts.debounceImmediate !== false; opts.cwd = opts.cwd || process.cwd(); this.options = opts; @@ -115,18 +116,36 @@ Gaze.prototype.emit = function () { }); } - // If cached doesnt exist, create a delay before running the next - // then emit the event + function _emit () { + Gaze.super_.prototype.emit.apply(self, args); + Gaze.super_.prototype.emit.apply(self, ['all', e].concat([].slice.call(args, 1))); + } + + // debounce var cache = this._cached[filepath] || []; if (cache.indexOf(e) === -1) { + // when this event is first seen within the `debounceDelay` window: + // - store event type to detect duplicate events within the + // `debounceDelay` window + // - set a timeout to remove the the filepath when no subsequent + // changes happen during `debounceDelay` window + // - emit the event helper.objectPush(self._cached, filepath, e); clearTimeout(timeoutId); timeoutId = setTimeout(function () { delete self._cached[filepath]; + if (!self.options.debounceImmediate) _emit(); + }, this.options.debounceDelay); + if (this.options.debounceImmediate) _emit(); + } else { + // when a duplicate event is seen within the `debounceDelay` window: + // - clear the existing timeout + // - set a new timeout to ensure the last event is emitted + clearTimeout(timeoutId); + timeoutId = setTimeout(function () { + delete self._cached[filepath]; + _emit(); }, this.options.debounceDelay); - // Emit the event and `all` event - Gaze.super_.prototype.emit.apply(self, args); - Gaze.super_.prototype.emit.apply(self, ['all', e].concat([].slice.call(args, 1))); } // Detect if new folder added to trigger for matching files within folder diff --git a/test/watch_test.js b/test/watch_test.js index 6e6bfaf..d64c122 100644 --- a/test/watch_test.js +++ b/test/watch_test.js @@ -15,6 +15,7 @@ function cleanUp (done) { 'nested/added.js', 'nested/.tmp', 'nested/sub/added.js', + 'edited.js' ].forEach(function (d) { try { var p = path.resolve(__dirname, 'fixtures', d); @@ -332,6 +333,52 @@ exports.watch = { this.close(); }); }, + debounceTiming: function (test) { + test.expect(1); + gaze('**/*', function (err, watcher) { + var lastContent = ''; + watcher.on('all', function (filepath) { + lastContent = fs.readFileSync(path.resolve(__dirname, 'fixtures', 'edited.js')).toString(); + }); + watcher.on('end', function () { + test.equal(lastContent, '4', 'should catch the last changed event.'); + test.done(); + }); + + var count = 0; + var timer = setInterval(function () { + fs.writeFileSync(path.resolve(__dirname, 'fixtures', 'edited.js'), count); + count++; + if (count > 4) { + clearTimeout(timer); + setTimeout(function () { watcher.close(); }, 1000); + } + }, 100); + }); + }, + debounceImmediate: function (test) { + test.expect(1); + gaze('**/*', { debounceImmediate: false }, function (err, watcher) { + var called = 0; + watcher.on('all', function (filepath) { + called += 1; + }); + watcher.on('end', function () { + test.equal(called, 1, 'should only detect one change event.'); + test.done(); + }); + + var count = 0; + var timer = setInterval(function () { + fs.writeFileSync(path.resolve(__dirname, 'fixtures', 'edited.js'), count); + count++; + if (count > 4) { + clearTimeout(timer); + setTimeout(function () { watcher.close(); }, 1000); + } + }, 100); + }); + } }; // These tests on Windows trigger the folder of a file added to that folder @@ -339,4 +386,4 @@ exports.watch = { if (process.platform === 'win32') { delete exports.watch['mkdirThenAddFile']; delete exports.watch['mkdirThenAddFileWithGruntFileWrite']; -} \ No newline at end of file +}