jiyinyiyong
jiyinyiyong

Reputation: 4713

Why fs.watchFile called twice in Node?

Ubuntu 12.04 Node v0.6.14 CoffeeScript 1.3.1

fs.watchFile coffee_eval, (e) ->
  console.log e
  result = spawn 'coffee', ['-bc', coffee_eval]
  msg = ''
  result.stderr.on 'data', (str) ->
    msg+= str
  result.stderr.on 'end', ->
    console.log 'msg: ', msg
  print "!! #{coffee_eval}\n"

Whole code on gist: https://gist.github.com/2621576

Every time I save a file which is watched, the main function is called twitce rather than once.
My Editor is Sumlime Text 2.

the output words can be see :
enter image description here

Upvotes: 3

Views: 5772

Answers (10)

snnsnn
snnsnn

Reputation: 13620

This is an old question but a common one which needs more up to date answer: It is the editor updating the content multiple times.

First and foremost, it is advised to use fs.watch instead. However you may experience same problem with fs.watch but it does not fire same event multiple times for a single change, you get that because your editor is updating the file content multiple times.

We can test this using a simple node server which writes to file when it receives a request:

const http = require('http');
const fs = require('fs');
const path = require('path');

const host = 'localhost';
const port = 3000;

const file = path.join(__dirname, 'config.json');

const requestListener = function (req, res) {
  const data = new Date().toString();
  fs.writeFileSync(file, data, { encoding: 'utf-8' });
  res.end(data);
};

const server = http.createServer(requestListener);

server.listen(port, host, () => {
  fs.watch(file, (eventType, filename) => {
    console.log({ eventType });
  });
  console.log(`Server is running on http://${host}:${port}`);
});

Visit the server and observe the output. You will see it logs single event:

Server is running on http://localhost:3000
{ eventType: 'change' }

If you edit the file in an editor like VSCode, you may see multiple events are logged.

That is because editors tend to use stream API for efficiency and read and write files in chunks which causes the change event fired multiple times depending on the chunk size and the file length.

Since writes have very short window of time, you can use stats to eliminate the duplicate events for trivial use cases:

let lastModified = 0;

fs.watch(file, (eventType, filename) => {
  stat(file).then(({ mtimeMs }) => {
    if ((mtimeMs - lastModified) > 50) {
      lastModified = mtimeMs;
      // Do your work here! It will run once!
      console.log({ eventType, filename });
    }
  });
});

Otherwise it is better to use debounce.

let timeoutId;
fs.watch(file, (eventType, filename) => {
  clearTimeout(timeoutId);
  timeoutId = setTimeout(() => {
    console.log({ eventType });
  }, 100);
});

Check this anwswer to see how you can use it with other fs methods: https://stackoverflow.com/a/75149864/7134134

Upvotes: 2

Tien Do
Tien Do

Reputation: 11069

The problem has been fixed (at least) with fs.watch now. I didn't try fs.watchFile because it's not recommended by NodeJS documentation.

  • One change event each time file is modified
  • 2 rename events when you changed a file name, one for current name and one for new name
  • One rename event for newly created file (copied from other location) or deleted file.

My environment: macOS 10.12.6 and NodeJS v11.10.1

Upvotes: 0

Tien Do
Tien Do

Reputation: 11069

It isn't directly related with the original question, but do you know what this sentence mean? (from fs.watch documentation).

Also note the listener callback is attached to the 'change' event fired by fs.FSWatcher, but it is not the same thing as the 'change' value of eventType.

Upvotes: 0

bormat
bormat

Reputation: 1379

The problem is still present, here is the way I have found.

var actionDone = {}    
fs.watch('.', function(x,filename) {
        var path = './'+filename;
        var stats = fs.statSync(path);
        let seconds = +stats.mtime;
        if(actionDone[filename] == seconds) return;
        actionDone[filename] = seconds
       //write your code here
});

We check if the last modified time is different before to continue.

Upvotes: 5

Artisan72
Artisan72

Reputation: 3600

Another suggestion for an npm module which is much better than fs.watch or fs.watchFile:

https://github.com/paulmillr/chokidar/

Upvotes: 0

Toadfish
Toadfish

Reputation: 1152

I solved this problem by flipping an 'ignore' flag from false to true every time an update was received, thereby ignoring every second event. BUT i also found that sometimes, a change to the file only resulted in one update. I'm not sure what causes this but it seemed to happen when updates were very frequent, and when those updates were appends (>>). I did not observe any instances of a single change triggering more than two events.

There's more discussion of the issue in this question. I also posted some example code for my solution there.

Upvotes: 0

tapatron
tapatron

Reputation: 78

If you are using underscore or lodash, you could consider using throttle and discard the calls on the trailing edge. A basic example would be

var fs = require('fs');
var _ = require("lodash");

function FileWatcher (fileName)
{
    this.file = fileName;
    this.onChange = _.throttle(this.trigger, 100, {leading: false});
}
FileWatcher.prototype.observe = function ()
{
    fs.watch(this.file, this.onChange);
}
FileWatcher.prototype.trigger = function ()
{
    console.log("file changed :)");
}

var fileToWatch = __dirname + "/package.json";
new FileWatcher(fileToWatch).observe();

Upvotes: 1

sffc
sffc

Reputation: 6424

To solve this problem, I keep track of the previous "file modified" timestamp and don't run my normal callback code if the value is the same.

var filename = "/path/to/file";
var previousMTime = new Date(0);
var watcher = fs.watch(filename, {
    persistent: false
}, function(){
    fs.stat(filename, function(err, stats){
        if(stats.mtime.valueOf() === previousMTime.valueOf()){
            console.log("File Update Callback Stopped (same revision time)");
            return;
        }
        previousMTime = stats.mtime;

        // do your interesting stuff down here
    });
});

Upvotes: 0

Bill
Bill

Reputation: 25555

I would suggest trying node-inotify-plusplus (https://github.com/coolaj86/node-inotify-plusplus) which has worked much better for me than fs.watchFile or fs.watch.

Upvotes: 1

mihai
mihai

Reputation: 38543

fs.watchFile is unstable. From the node docs:

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

Stability: 2 - Unstable. Use fs.watch instead, if available.

You can try fs.watch, but unfortunately it may suffer from the same problem. I had the same issue with fs.watch on windows, when trying to create a similar monitor script.

The workaround was to log the time when the modification occurs and ignore the second change if it was triggered withing a few milliseconds. A bit ugly but it worked.

Upvotes: 6

Related Questions