Node.js'te bir dosya ya da klasördeki değişiklikleri izlemek için fs.watch() kullanılır: watch('klasor', (eventType, filename) => {}) işletim sisteminin bildirim mekanizmasına bağlanır ve her değişiklikte 'rename' ya da 'change' türünde bir olay verir. Aynı işin async iterator döndüren biçimi fsPromises.watch(), düzenli aralıklarla stat çağırarak çalışan daha yavaş alternatifi ise fs.watchFile()'dır. Belgeler mümkün olduğunda fs.watch kullanılmasını önerir ama bu API'nin platformlar arasında "yüzde yüz tutarlı olmadığını" da açıkça söyler.
Bu yazıda üç izleme yöntemini, tek bir yazma işleminin neden birden fazla olay ürettiğini, olayları birleştirmeyi (debounce), alt klasörleri izlemeyi ve ignore seçeneğini, AbortSignal ile durdurmayı ve belgelerdeki platform uyarılarını Node.js v26.13.0 ile çalıştırılmış örneklerle anlatıyoruz. Örnekler Windows'ta çalıştırıldı; olay sayıları ve sıraları Linux ve macOS'ta farklı olabilir, bunu her örnekte belirtiyoruz.
fs.watch nasıl çalışır?

fs.watch(filename[, options][, listener]) bir fs.FSWatcher nesnesi döndürür. Dinleyici fonksiyon iki parametre alır: olay türü (eventType) ve değişikliği tetikleyen dosyanın adı (filename). Belgeye göre çoğu platformda bir dosya adı klasörde ortaya çıktığında ya da kaybolduğunda 'rename', içerik değiştiğinde 'change' verilir. Aşağıdaki örnek bir klasörü izliyor, içine bir dosya yazıyor, sonra dosyaya ekleme yapıyor ve izleyiciyi kapatıyor:
import { watch } from 'node:fs';
import { mkdir, writeFile, appendFile, rm } from 'node:fs/promises';
import { setTimeout as bekle } from 'node:timers/promises';
await rm('izlenen', { recursive: true, force: true });
await mkdir('izlenen');
const olaylar = [];
const izleyici = watch('izlenen', (eventType, filename) => {
olaylar.push(`${eventType} ${filename}`);
});
await bekle(100);
await writeFile('izlenen/yeni.txt', 'a');
await bekle(100);
await appendFile('izlenen/yeni.txt', 'b');
await bekle(100);
izleyici.close();
console.log(olaylar);
// çıktı: [
// çıktı: 'rename yeni.txt',
// çıktı: 'change yeni.txt',
// çıktı: 'change yeni.txt',
// çıktı: 'change yeni.txt'
// çıktı: ]İki yazma işlemine karşılık dört olay geldi: dosya oluştuğu için bir 'rename', ardından üç 'change'. Windows'ta bu sonucu art arda dört çalıştırmada aynı aldık; başka bir işletim sisteminde sayı farklı olabilir. Buradan çıkan ders şudur: olay sayısını yapılan işlem sayısına eşit saymayın. Bir yazma işlemi işletim sistemi düzeyinde birden fazla değişiklik (içerik, boyut, zaman damgası) olarak bildirilebilir.
Belgede ince bir ayrım da vardır: dinleyici fonksiyon FSWatcher'ın 'change' olayına bağlanır, ama bu olay adı eventType değeri olan 'change' ile aynı şey değildir. 'rename' türündeki değişiklikler de FSWatcher'ın 'change' olayıyla gelir. FSWatcher bir EventEmitter'dır; 'close' ve 'error' olaylarını da dinleyebilirsiniz. Olay yayıcı modeli için EventEmitter yazımıza bakabilirsiniz.
Olayları birleştirmek (debounce)
Bir ayar klasörünü izleyip değişiklikte yapılandırmayı yeniden yüklemek istediğinizi düşünün. Yukarıda gördüğümüz gibi tek bir kayıt birden fazla olay üretebilir; her olayda yeniden yükleme yapmak gereksizdir. Yaygın çözüm, son olaydan sonra kısa bir süre bekleyip işi bir kez yapmaktır:
import { watch } from 'node:fs';
import { mkdir, writeFile, rm } from 'node:fs/promises';
import { setTimeout as bekle } from 'node:timers/promises';
await rm('ayarlar', { recursive: true, force: true });
await mkdir('ayarlar');
let zamanlayici;
let yenileme = 0;
const izleyici = watch('ayarlar', () => {
clearTimeout(zamanlayici);
zamanlayici = setTimeout(() => {
yenileme++;
}, 50);
});
await bekle(100);
await writeFile('ayarlar/a.json', '{}');
await writeFile('ayarlar/a.json', '{"x":1}');
await writeFile('ayarlar/b.json', '{}');
await bekle(300);
izleyici.close();
console.log('ayar yeniden yüklendi:', yenileme);
// çıktı: ayar yeniden yüklendi: 1Üç yazma işlemi ve bunların ürettiği tüm olaylar tek bir yeniden yüklemeye dönüştü. Bekleme süresini (burada 50 ms) uygulamanızın ihtiyacına göre seçin; çok kısa tutarsanız birleştirme işe yaramaz, çok uzun tutarsanız değişiklik geç algılanır. setTimeout, clearTimeout ve timers/promises için timers yazımıza bakabilirsiniz.
Alt klasörleri izlemek ve ignore seçeneği
recursive: true verildiğinde bir klasörün tüm alt klasörleri de izlenir. Belgedeki değişiklik kaydına göre Linux, AIX ve IBMi için recursive desteği v19.1.0 ile eklendi; seçenek yalnızca desteklenen platformlarda geçerlidir. ignore seçeneği ise belirli dosyaların olaylarını filtreler: string değerler glob deseni olarak (minimatch ile), RegExp değerler dosya adına karşı sınanır; fonksiyon verirseniz dosya adını alır ve yok sayılacaksa true döndürür. Birden fazla deseni dizi olarak verebilirsiniz:
import { watch } from 'node:fs';
import { mkdir, writeFile, rm } from 'node:fs/promises';
import { setTimeout as bekle } from 'node:timers/promises';
await rm('src', { recursive: true, force: true });
await mkdir('src/alt', { recursive: true });
const adlar = new Set();
const izleyici = watch('src', { recursive: true, ignore: '**/*.tmp' }, (tur, ad) => {
if (ad) adlar.add(ad);
});
await bekle(100);
await writeFile('src/app.js', '1');
await writeFile('src/alt/yardim.js', '2');
await writeFile('src/alt/taslak.tmp', '3');
await bekle(200);
izleyici.close();
console.log([...adlar].sort());
// çıktı: [ 'alt', 'alt\\yardim.js', 'app.js' ]taslak.tmp desene uyduğu için listeye girmedi. Alt klasördeki dosya, izlenen klasöre göre göreli yoluyla geldi; Windows'ta ayırıcı \ olduğu için 'alt\\yardim.js' görünüyor. İçine dosya yazılan alt klasörünün kendisi için de bir olay geldi. Linux'ta aynı kodun hangi adları vereceği değişebilir; olayların içeriğine değil, "bir şey değişti, ilgili yeri yeniden oku" mantığına güvenmek daha sağlamdır.
fsPromises.watch: async iterator ve AbortSignal

node:fs/promises modülündeki watch() fonksiyonu callback yerine, her değişiklikte { eventType, filename } nesnesi üreten bir async iterator döndürür. for await...of ile tüketilir ve bir AbortSignal ile durdurulur. Sinyal tetiklendiğinde döngü AbortError fırlatır; bu beklenen bir sonlanmadır ve yakalanmalıdır:
import { watch } from 'node:fs/promises';
import { mkdir, writeFile, rm } from 'node:fs/promises';
import { setTimeout as bekle } from 'node:timers/promises';
await rm('gelen', { recursive: true, force: true });
await mkdir('gelen');
const ac = new AbortController();
(async () => {
await bekle(100);
await writeFile('gelen/siparis.json', '{}');
await bekle(200);
ac.abort();
})();
const gorulen = new Set();
try {
for await (const { eventType, filename } of watch('gelen', { signal: ac.signal })) {
gorulen.add(`${eventType}:${filename}`);
}
} catch (err) {
console.log(err.name);
}
console.log([...gorulen]);
// çıktı: AbortError
// çıktı: [ 'rename:siparis.json', 'change:siparis.json' ]Örnekte yinelenen olayları ayıklamak için bir Set kullandık. Bu biçime özgü iki seçenek vardır: maxQueue (varsayılan 2048) iterator'ın iki adımı arasında kuyrukta bekletilecek olay sayısını belirler; overflow ise kuyruk dolduğunda ne olacağını seçer. Varsayılan 'ignore' taşan olayları atar ve bir uyarı yayar, 'error' ise hata fırlatır. Döngü gövdesinde uzun süren bir iş yapıyorsanız olayların birikebileceğini unutmayın. Belge, fs.watch için geçerli tüm platform uyarılarının bu fonksiyon için de geçerli olduğunu belirtir. AbortController kullanımının ayrıntıları AbortController yazımızda.
fs.watchFile: stat ile yoklama
fs.watchFile(filename[, options], listener) farklı bir yöntem kullanır: işletim sisteminden bildirim beklemek yerine dosyayı belirli aralıklarla stat ile yoklar (polling). Varsayılan aralık 5007 milisaniyedir ve interval seçeneğiyle değiştirilebilir. Dinleyici her seferinde güncel ve önceki fs.Stats nesnelerini alır. Belgeye göre dinleyici dosyaya her erişildiğinde çağrılabilir; yalnızca içerik değişikliğini yakalamak için mtimeMs değerlerini karşılaştırmanız gerekir:
import { watchFile, unwatchFile } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { setTimeout as bekle } from 'node:timers/promises';
await writeFile('durum.txt', 'v1');
watchFile('durum.txt', { interval: 50 }, (simdi, once) => {
if (simdi.mtimeMs !== once.mtimeMs) {
console.log('değişti, yeni boyut:', simdi.size);
}
});
await bekle(200);
await writeFile('durum.txt', 'v2 içerik');
await bekle(300);
unwatchFile('durum.txt');
console.log('izleme bitti');
// çıktı: değişti, yeni boyut: 10
// çıktı: izleme bittiunwatchFile(filename) o dosyanın tüm dinleyicilerini kaldırır; ikinci parametre olarak belirli bir dinleyiciyi verirseniz yalnızca onu kaldırır. İzlenmeyen bir dosya için çağırmak hata vermez. watchFile'ın kullanışlı bir özelliği dosya henüz yokken de çalışabilmesidir: belgeye göre işlem ENOENT ile karşılaşırsa dinleyici bir kez, tüm alanları sıfırlanmış (tarihler için Unix başlangıcı) Stats nesneleriyle çağrılır; dosya sonradan oluşturulursa dinleyici yeniden çağrılır.
Belgede açıkça yazdığı gibi fs.watch, fs.watchFile ve fs.unwatchFile'dan daha verimlidir ve mümkün olduğunda onun yerine kullanılmalıdır. watchFile'ı, fs.watch'ın çalışmadığı ortamlar için (aşağıdaki uyarılara bakın) bir yedek olarak düşünün; belge onu "daha yavaş ve daha az güvenilir" olarak niteler.
Olmayan yolu izlemek: throwIfNoEntry
fs.watch var olmayan bir yol için varsayılan olarak ENOENT fırlatır. v26.1.0 ile eklenen throwIfNoEntry seçeneği (varsayılan true) bu davranışı kontrol eder; false verdiğinizde hata fırlatılmaz ve bir FSWatcher döner:
import { watch } from 'node:fs';
try {
watch('olmayan-klasor');
} catch (err) {
console.log(err.code, err.syscall);
}
const izleyici = watch('olmayan-klasor', { throwIfNoEntry: false });
console.log(izleyici.constructor.name);
izleyici.close();
// çıktı: ENOENT watch
// çıktı: FSWatcherBelge, false durumunda dönen izleyicinin yol sonradan oluşturulursa olay üretip üretmeyeceği hakkında ayrıntı vermez. Bu yüzden henüz var olmayan bir dosyayı beklemeniz gerekiyorsa, belgede davranışı tanımlanmış olan watchFile ya da üst klasörü izleyip 'rename' olayını beklemek daha öngörülebilir bir yoldur. Bu seçenek v26.1.0'dan eski sürümlerde yoktur.
Sürecin çıkmasını engellememek: persistent ve unref
Varsayılan olarak bir izleyici aktif olduğu sürece Node.js süreci çıkmaz; persistent seçeneği true'dur. Bir betiğin, işi bitince izleyici açık kalsa bile kapanmasını istiyorsanız persistent: false verebilir ya da izleyici üzerinde unref() çağırabilirsiniz. ref() bunu geri alır:
import { watch } from 'node:fs';
const izleyici = watch('.', () => {});
izleyici.unref();
console.log('süreç beklemeden çıkacak');
// çıktı: süreç beklemeden çıkacakunref() olmasaydı bu betik siz Ctrl+C ile durdurana kadar çalışmaya devam ederdi. Aynı yöntemler watchFile'ın döndürdüğü fs.StatWatcher nesnesinde de vardır. Zamanlayıcılardaki ref/unref mantığı da aynıdır; olay döngüsünü canlı tutan kaynaklar olay döngüsü fazları yazımızda anlatılıyor.
Belgelerdeki platform uyarıları

fs.watch işletim sisteminin dosya değişikliği bildirim mekanizmasına dayanır. Belgeye göre Linux'ta inotify, BSD'lerde kqueue, macOS'ta dosyalar için kqueue ve klasörler için FSEvents, Windows'ta ReadDirectoryChangesW, SunOS ailesinde event ports, AIX'te etkinleştirilmesi gereken AHAFS kullanılır. IBM i sistemlerinde bu özellik desteklenmez. Alttaki mekanizma kullanılamıyorsa fs.watch çalışmaz ve hata fırlatabilir.
- Ağ dosya sistemleri ve sanallaştırma: NFS, SMB gibi ağ dosya sistemlerinde ya da Vagrant, Docker gibi sanallaştırma yazılımlarıyla paylaşılan host klasörlerinde izleme güvenilmez, bazen imkânsız olabilir. Bu durumda belge
watchFile'ı alternatif olarak gösterir. - Inode davranışı: Linux ve macOS'ta
fs.watchyolu bir inode'a çözer ve o inode'u izler. İzlenen dosya silinip yeniden oluşturulursa yeni bir inode alır; izleyici silme olayını verir ama eski inode'u izlemeye devam eder ve yeni dosyanın olaylarını vermez. Belge bunun beklenen davranış olduğunu söyler. Böyle bir durumda tek dosya yerine bulunduğu klasörü izlemek bir seçenektir; belgeye göre çoğu platformda bir dosya adı klasörde yeniden ortaya çıktığında'rename'olayı verilir. - Windows'ta klasör taşıma: izlenen klasör taşınır ya da yeniden adlandırılırsa hiçbir olay verilmez; klasör silinirse
EPERMhatası bildirilir. - Güvenlik: belge,
fs.watch'ın dosya sistemindeki kötü niyetli işlemlere karşı koruma sağlamadığını vurgular. Örneğin Windows'ta izleme belirli dosyalar yerine klasör düzeyinde yapıldığı için aynı adlı başka bir dosya yerine konabilir ve değişiklikler yeni dosya için bildirilir. - AIX: dosyalar ömürleri boyunca aynı inode'u korur; izlenen bir dosyayı kaydedip kapatmak iki bildirim üretir.
Bir başka teknik ayrıntı: belgedeki iş parçacığı havuzu notuna göre callback ve Promise tabanlı tüm fs API'leri libuv iş parçacığı havuzunu kullanır, tek istisna dosya izleyicileridir. Yani çok sayıda izleyici açmak, dosya okuma ve yazma işlemlerinin kullandığı havuzu meşgul etmez.
Kod değişince süreci yeniden başlatmak: --watch
Geliştirme sırasında amacınız "dosya değişince uygulamayı yeniden başlat" ise kendi izleyicinizi yazmanıza gerek yoktur. Node.js'in --watch bayrağı giriş dosyasını ve onun içe aktardığı modülleri izler, değişiklikte süreci yeniden başlatır. Bu bayrak v22.0.0 ve v20.13.0'da kararlı hale geldi. --watch-path ile izlenecek yolları kendiniz belirleyebilirsiniz; belgeye göre bu seçenek yalnızca macOS ve Windows'ta desteklenir, diğer platformlarda ERR_FEATURE_UNAVAILABLE_ON_PLATFORM fırlatılır:
node --watch index.js
# çıktı: basladi
# çıktı: Completed running 'index.js'. Waiting for file changes before restarting...
node --watch-path=./src --watch-path=./tests index.jsProgram bitse bile süreç kapanmaz, değişiklik bekler; durdurmak için Ctrl+C kullanırsınız. Belgeye göre --watch bir dosya yolu gerektirir, --check, --eval, --interactive ve REPL ile birleştirilemez; --run ile birlikte verilirse --run öncelik kazanır ve izleme kipi yok sayılır. Betik çalıştırma seçeneklerinin tamamı node komutu ile betik çalıştırma yazımızda anlatılıyor.
Üç yöntemin karşılaştırması
| Özellik | fs.watch | fsPromises.watch | fs.watchFile |
|---|---|---|---|
| Mekanizma | İşletim sistemi bildirimi | İşletim sistemi bildirimi | stat ile yoklama |
| Kullanım biçimi | Callback veya FSWatcher olayları | for await ile async iterator | Callback (simdi, once) |
| Klasör izleme | Evet, recursive destekli platformlarda | Evet | Tek dosya odaklı |
| Durdurma | close() veya signal | signal ile AbortError | unwatchFile() |
| Ağ dosya sistemi, Docker | Güvenilmez olabilir | Güvenilmez olabilir | Çalışır ama yavaş |
| Belgenin önerisi | Mümkün olduğunda tercih edin | fs.watch ile aynı uyarılar | Yalnızca gerekirse |
Sık yapılan hatalar
- Her olayda ağır iş yapmak: tek bir kayıt birden fazla olay üretebilir; işi debounce ile birleştirin.
- filename değerine güvenmek: belgeye göre
filenameher zaman verilmez;nulldurumunu ele alın. - Silinip yeniden yazılan dosyayı izlemek: Linux ve macOS'ta izleyici eski inode'a bağlı kalır ve yeni dosyanın olaylarını vermez; dosyanın bulunduğu klasörü izlemeyi düşünün.
- Docker ya da ağ paylaşımında fs.watch beklemek: belge bu ortamlarda izlemenin güvenilmez olabileceğini söyler; gerekirse
watchFileile yoklama yapın. - watchFile'da her çağrıyı değişiklik sanmak: dinleyici erişimde de çağrılabilir;
curr.mtimeMsileprev.mtimeMs'i karşılaştırın. - İzleyiciyi kapatmamak:
close()ya daunwatchFile()çağrılmazsa süreç çıkmaz ve kaynak açık kalır. - Geliştirmede yeniden başlatma için izleyici yazmak: çoğu durumda
node --watchyeterlidir.
Sık Sorulan Sorular
fs.watch ile fs.watchFile arasındaki fark nedir?
fs.watch işletim sisteminin değişiklik bildirimlerini kullanır, hızlıdır ve klasörleri izleyebilir. fs.watchFile dosyayı belirli aralıklarla stat ile yoklar; daha yavaş ve daha az güvenilirdir ama bildirim mekanizmasının çalışmadığı ortamlarda yedek olarak kullanılabilir.
Neden tek bir değişiklik için birden fazla olay alıyorum?
İşletim sistemi bir yazma işlemini birden fazla değişiklik olarak bildirebilir. Örneğimizde Windows'ta bir dosya oluşturma ve bir ekleme dört olay üretti. Olayları kısa bir bekleme süresiyle birleştirmek (debounce) standart çözümdür.
Alt klasörleri de izleyebilir miyim?
Evet, recursive: true seçeneğiyle. Belgeye göre seçenek yalnızca desteklenen platformlarda geçerlidir; Linux, AIX ve IBMi desteği v19.1.0 ile eklendi. Gelen dosya adları izlenen klasöre göre göreli olur.
İzlemeyi nasıl durdururum?
fs.watch için izleyici.close() çağırın ya da seçeneklere verdiğiniz AbortController'ı iptal edin. fsPromises.watch döngüsü sinyalle durur ve AbortError fırlatır. fs.watchFile için fs.unwatchFile(dosya) kullanılır.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Node.js öğrenme rehberi · fs.stat ve Stats nesnesi · Klasör işlemleri · node --watch ile çalıştırma · AbortController · Node.js nedir