Watching drives every development server you use: change a file, the process reloads. fsPromises.watch gives you an async iterator of change events, and an AbortSignal stops it.
import { watch, writeFile, mkdir, rm } from 'node:fs/promises';
import { setTimeout as delay } from 'node:timers/promises';
const ac = new AbortController();
setTimeout(() => ac.abort(), 1200).unref(); // stop the watcher after 1.2 s
const pending = new Set();
let timer = null;
(async () => { // make some changes to observe
await delay(100);
await writeFile('src/app.js', 'const a = 1;\n');
await mkdir('src/lib', { recursive: true });
await writeFile('src/lib/util.js', 'export const x = 1;\n');
})();
try {
const options = { recursive: true, signal: ac.signal };
for await (const { eventType, filename } of watch('src', options)) {
pending.add(`${filename} (${eventType})`);
clearTimeout(timer); // debounce the burst
timer = setTimeout(() => console.log('changed:', [...pending].sort().join(', ')), 120);
}
} catch (err) {
if (err.name !== 'AbortError') throw err;
console.log('watcher: stopped cleanly');
}
await rm('src/lib', { recursive: true, force: true });changed: app.js (change), lib (change), lib (rename), lib\util.js (change), lib\util.js (rename) watcher: stopped cleanly
Three things deserve attention. One logical edit produced several events — creating lib/util.js fired both rename and change, and touched the parent directory too — which is why the debounce timer exists. eventType is only ever 'rename' or 'change', never "created" or "deleted"; stat the path to tell them apart. And aborting rejects the iterator with an AbortError, so the catch block is mandatory.
fs.watchFile() is the fallback: it polls stat on an interval, so it works anywhere at the cost of latency and CPU. For production-grade watching, chokidar 5.0.0 12,245 (github.com/paulmillr/chokidar (https://github.com/paulmillr/chokidar 12,245 ), npm 2,036 i chokidar) normalizes the platform differences, waits for a file to finish being written before reporting it, and emits real add/change/unlink events. Before installing it, check whether node --watch (Watch Mode and Scripts) already solves your problem.