42
 fs.watch( 'example.xml', function ( curr, prev ) {
   // on file change we can read the new xml
   fs.readFile( 'example.xml','utf8', function ( err, data ) {
     if ( err ) throw err;
     console.dir(data);
     console.log('Done');
   });
 });

OUTPUT:

  • some data
  • Done X 1
  • some data
  • Done X 2

It is my usage fault or ..?

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445

17 Answers17

48

The fs.watch api:

  1. is unstable
  2. has known "behaviour" with regards repeated notifications. Specifically, the windows case being a result of windows design, where a single file modification can be multiple calls to the windows API
Pero P.
  • 25,813
  • 9
  • 61
  • 85
23

I debounce this with a setTimeout:

var fsTimeout

fs.watch('file.js', function(e) {
        
    if (!fsTimeout) {
        console.log('file.js %s event', e)
        fsTimeout = setTimeout(function() { fsTimeout=null }, 5000) // give 5 seconds for multiple events
    }
}
General Grievance
  • 4,555
  • 31
  • 31
  • 45
jwymanm
  • 331
  • 2
  • 3
20

I suggest to work with chokidar (https://github.com/paulmillr/chokidar) which is much better than fs.watch:

Commenting its README.md:

Node.js fs.watch:

  • Doesn't report filenames on OS X.
  • Doesn't report events at all when using editors like Sublime on OS X.
  • Often reports events twice.
  • Emits most changes as rename.
  • Has a lot of other issues
  • Does not provide an easy way to recursively watch file trees.

Node.js fs.watchFile:

  • Almost as bad at event handling.
  • Also does not provide any recursive watching.
  • Results in high CPU utilization.
Artisan72
  • 3,052
  • 3
  • 23
  • 30
12

If you need to watch your file for changes then you can check out my small library on-file-change. It checks file sha1 hash between fired change events.

Explanation of why we have multiple fired events:

You may notice in certain situations that a single creation event generates multiple Created events that are handled by your component. For example, if you use a FileSystemWatcher component to monitor the creation of new files in a directory, and then test it by using Notepad to create a file, you may see two Created events generated even though only a single file was created. This is because Notepad performs multiple file system actions during the writing process. Notepad writes to the disk in batches that create the content of the file and then the file attributes. Other applications may perform in the same manner. Because FileSystemWatcher monitors the operating system activities, all events that these applications fire will be picked up.

Source

Jan Święcki
  • 1,611
  • 2
  • 16
  • 28
  • @ImanMohamadi it depends. I think if file changes not too often and filesize is not too big you shouldn't worry. When I used it I didn't notice performance issues. – Jan Święcki Jun 04 '14 at 14:25
  • I've added explanation I found by accident. – Jan Święcki Mar 04 '16 at 14:15
  • For more perf. we can use async/await with node's fsPromises. It would probably be overkill to create a fork for each CPU the machine has, but it would help for the checksums. Maybe another checksum is more efficient. We can also check file size before any checksum. Adding an ignore list like .gitignore could help too. – Rivenfall Aug 27 '19 at 08:39
6

My custom solution

I personally like using return to prevent a block of code to run when checking something, so, here is my method:

var watching = false;
fs.watch('./file.txt', () => {
    if(watching) return;
    watching = true;

    // do something

    // the timeout is to prevent the script to run twice with short functions
    // the delay can be longer to disable the function for a set time
    setTimeout(() => {
        watching = false;
    }, 100);
};

Feel free to use this example to simplify your code. It may NOT be better than using a module from others, but it works pretty well!

Lasse Brustad
  • 119
  • 1
  • 6
1

Similar/same problem. I needed to do some stuff with images when they were added to a directory. Here's how I dealt with the double firing:

var fs = require('fs');
var working = false;

fs.watch('directory', function (event, filename) {
  if (filename && event == 'change' && active == false) {
    active = true;

    //do stuff to the new file added

    active = false;
});

It will ignore the second firing until if finishes what it has to do with the new file.

Bernardo SOUSA
  • 476
  • 4
  • 15
1

I'm dealing with this issue for the first time, so all of the answers so far are probably better than my solution, however none of them were 100% suitable for my case so I came up with something slightly different – I used a XOR operation to flip an integer between 0 and 1, effectively keeping track of and ignoring every second event on the file:

var targetFile = "./watchThis.txt"; 
var flippyBit = 0; 

fs.watch(targetFile, {persistent: true}, function(event, filename) {

      if (event == 'change'){
        if (!flippyBit) {
          var data = fs.readFile(targetFile, "utf8", function(error, data) {
            gotUpdate(data);
          })
        } else {
          console.log("Doing nothing thanks to flippybit.");              
        }
        flipBit(); // call flipBit() function
      }
    });

// Whatever we want to do when we see a change
function gotUpdate(data) {
    console.log("Got some fresh data:");
    console.log(data);
    }


// Toggling this gives us the "every second update" functionality

function flipBit() {
    flippyBit = flippyBit ^ 1;
}

I didn't want to use a time-related function (like jwymanm's answer) because the file I'm watching could hypothetically get legitimate updates very frequently. And I didn't want to use a list of watched files like Erik P suggests, because I'm only watching one file. Jan Święcki's solution seemed like overkill, as I'm working on extremely short and simple files in a low-power environment. Lastly, Bernado's answer made me a little nervous – it would only ignore the second update if it arrived before I'd finished processing the first, and I can't handle that kind of uncertainty. If anyone were to find themselves in this very specific scenario, there might be some merit to the approach I used? If there's anything massively wrong with it please do let me know/edit this answer, but so far it seems to work well?

NOTE: Obviously this strongly assumes that you'll get exactly 2 events per real change. I carefully tested this assumption, obviously, and learned its limitations. So far I've confirmed that:

  • Modifying a file in Atom editor and saving triggers 2 updates
  • touch triggers 2 updates
  • Output redirection via > (overwriting file contents) triggers 2 updates
  • Appending via >> sometimes triggers 1 update!*

I can think of perfectly good reasons for the differing behaviours but we don't need to know why something is happening to plan for it – I just wanted to stress that you'll want to check for yourself in your own environment and in the context of your own use cases (duh) and not trust a self-confessed idiot on the internet. That being said, with precautions taken I haven't had any weirdness so far.

* Full disclosure, I don't actually know why this is happening, but we're already dealing with unpredictable behaviour with the watch() function so what's a little more uncertainty? For anyone following along at home, more rapid appends to a file seem to cause it to stop double-updating but honestly, I don't really know, and I'm comfortable with the behaviour of this solution in the actual case it'll be used, which is a one-line file that will be updated (contents replaced) like twice per second at the fastest.

Marin Sagovac
  • 3,932
  • 5
  • 23
  • 53
Toadfish
  • 1,112
  • 1
  • 12
  • 22
0

first is change and the second is rename

we can make a difference from the listener function

function(event, filename) {

}

The listener callback gets two arguments (event, filename). event is either 'rename' or 'change', and filename is the name of the file which triggered the event.

// rm sourcefile targetfile
fs.watch( sourcefile_dir , function(event, targetfile)){
    console.log( targetfile, 'is', event)
}

as a sourcefile is renamed as targetfile, it's will call three event as fact

null is rename // sourcefile not exist again
targetfile is rename
targetfile is change

notice that , if you want catch all these three evnet, watch the dir of sourcefile

liandong
  • 17
  • 1
0

I somtimes get multible registrations of the Watch event causing the Watch event to fire several times. I solved it by keeping a list of watching files and avoid registering the event if the file allready is in the list:

 var watchfiles = {};

function initwatch(fn, callback) {
    if watchlist[fn] {
        watchlist[fn] = true;
        fs.watch(fn).on('change', callback);
    }
}

......

Erik P
  • 129
  • 5
0

Like others answers says... This got a lot of troubles, but i can deal with this in this way:

var folder = "/folder/path/";

var active = true; // flag control

fs.watch(folder, function (event, filename) {
    if(event === 'rename' && active) { //you can remove this "check" event
        active = false;

        // ... its just an example
        for (var i = 0; i < 100; i++) {
            console.log(i);
        }

        // ... other stuffs and delete the file
        if(!active){
            try {
                fs.unlinkSync(folder + filename);
            } catch(err) {
                console.log(err);
            }
            active = true
        }
    }
});

Hope can i help you...

0

Easiest solution:

const watch = (path, opt, fn) => {
  var lock = false
  fs.watch(path, opt, function () {
    if (!lock) {
      lock = true
      fn()
      setTimeout(() => lock = false, 1000)
    }
  })
}
watch('/path', { interval: 500 }, function () {
  // ...
})
Arthur Ronconi
  • 2,290
  • 25
  • 25
0

I was downloading file with puppeteer and once a file saved, I was sending automatic emails. Due to problem above, I noticed, I was sending 2 emails. I solved by stopping my application using process.exit() and auto-start with pm2. Using flags in code didn't saved me.

If anyone has this problem in future, one can use this solution as well. Exit from program and restart with monitor tools automatically.

Yunus
  • 123
  • 7
0

Here's my simple solution. It works well every time.

// Update obj as file updates
obj = JSON.parse(fs.readFileSync('./file.json', 'utf-8'));
fs.watch('./file.json', () => {
  const data = JSON.parse(fs.readFileSync('./file.json', 'utf-8') || '{}');
  if(Object.entries(data).length > 0) { // This checks fs.watch() isn't false-firing
    obj = data;
    console.log('File actually changed: ', obj)
  }
});
Esteban Ortega
  • 155
  • 1
  • 8
0

I came across the same issue. If you don't want to trigger multiple times, you can use a debounce function.

 fs.watch( 'example.xml', _.debounce(function ( curr, prev ) {
   // on file change we can read the new xml
   fs.readFile( 'example.xml','utf8', function ( err, data ) {
     if ( err ) throw err;
     console.dir(data);
     console.log('Done');
   });
 }, 100));
Marvin Xu
  • 139
  • 7
0

Debouncing The Observer

A solution I arrived at was that (a) there needs to be a workaround for the problem in question and, (b), there needs to be a solution to ensure multiple rapid Ctrl+s actions do not cause Race Conditions. Here's what I have...

./**/utilities.js (somewhere)
export default {
    ...
    debounce(fn, delay) {  // #thxRemySharp https://remysharp.com/2010/07/21/throttling-function-calls/
        var timer = null;
        
        return function execute(...args) {
            var context = this;
            clearTimeout(timer);
            timer = setTimeout(fn.bind(context, ...args), delay);
        };
    },
    ...
};
./**/file.js (elsewhere)
import utilities from './**/utilities.js';  // somewhere
...
function watch(server) {
    const debounced = utilities.debounce(observeFilesystem.bind(this, server), 1000 * 0.25);
    const observers = new Set()
        .add( fs.watch('./src', debounced) )
        .add( fs.watch('./index.html', debounced) )
        ;
    console.log(`watching... (${observers.size})`);
    
    return observers;
}
    
function observeFilesystem(server, type, filename) {
    if (!filename) console.warn(`Tranfer Dev Therver: filesystem observation made without filename for type ${type}`);
    console.log(`Filesystem event occurred:`, type, filename);
    server.close(handleClose);
}
...

This way, the observation-handler that we pass into fs.watch is [in this case a bound bunction] which gets debounced if multiple calls are made less than 1000 * 0.25 seconds (250ms) apart from one another.

It may be worth noting that I have also devised a pipeline of Promises to help avoid other types of Race Conditions as the code also leverages other callbacks. Please also note the attribution to Remy Sharp whose debounce function has repeatedly proven very useful over the years.

Cody
  • 9,785
  • 4
  • 61
  • 46
0
watcher = fs.watch( 'example.xml', function ( curr, prev ) {
   watcher.close();
   fs.readFile( 'example.xml','utf8', function ( err, data ) {
     if ( err ) throw err;
     console.dir(data);
     console.log('Done');
   });
 });

I had similar similar problem but I was also reading the file in the callback which caused a loop.

This is where I found how to close watcher: How to close fs.watch listener for a folder

0

NodeJS does not fire multiple events for a single change, it is the editor you are using updating the file multiple times.

Editors use stream API for efficiency, they read and write data in chunks which causes multiple updates depending on the chunks size and the amount of content. Here is a snippet to test if fs.watch fires multiple events:

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}`);
});

I believe a simple solution would be checking for the last modified timestamp:

let lastModified;

fs.watch(file, (eventType, filename) => {
  stat(file).then(({ mtimeMs }) => {
    if (lastModified !== mtimeMs) {
      lastModified = mtimeMs;
      console.log({ eventType, filename });
    }
  });
});

Please note that you need to use all-sync or all-async methods otherwise you will have issues:

Update the file in a editor, you will see only single event is logged:

const http = require('http');
const host = 'localhost';
const port = 3000;
const fs = require('fs');
const path = require('path');
const file = path.join(__dirname, 'config.json');

let lastModified;
const requestListener = function (req, res) {
  const data = Date.now().toString();
  
  fs.writeFileSync(file, data, { encoding: 'utf-8' });
  lastModified = fs.statSync(file).mtimeMs;

  res.end(data);
};

const server = http.createServer(requestListener);

server.listen(port, host, () => {
  fs.watch(file, (eventType, filename) => {
    const mtimeMs = fs.statSync(file).mtimeMs;
    if (lastModified !== mtimeMs) {
      lastModified = mtimeMs;
      console.log({ eventType });
    }
  });

  console.log(`Server is running on http://${host}:${port}`);
});

Few notes on the alternative solutions: Storing files for comparison will be memory inefficient especially if you have large files, taking file hashes will be expensive, custom flags are hard to keep track of, especially if you are going to detect changes made by other applications, and lastly unsubscribing and re-subscribing requires unnecessary juggling.

If you don't need an instant result, you can use setTimout to debounce successive events:

let timeoutId;
fs.watch(file, (eventType, filename) => {

  clearTimeout(timeoutId);
  timeoutId = setTimeout(() => {
    console.log({ eventType });
  }, 100);

});
snnsnn
  • 10,486
  • 4
  • 39
  • 44