Node.js'te EventEmitter, adlandırılmış olaylar yayan (emit) ve bu olaylara bağlanan fonksiyonları (listener, dinleyici) çağıran sınıftır. node:events modülünden gelir; on() ile dinleyici eklersiniz, emit() ile olayı tetiklersiniz, once() ile yalnızca bir kez çalışacak dinleyici kaydedersiniz. En önemli kural şudur: emit() çağrıldığında dinleyiciler senkron olarak, kayıt sırasıyla çalışır ve 'error' olayının hiç dinleyicisi yoksa hata fırlatılır, süreç kapanır.
Node.js çekirdeğinin büyük bölümü bu yapının üzerine kuruludur: bir net.Server her bağlantıda, bir dosya okuma akışı dosya açıldığında, bir stream veri geldiğinde olay yayar. Bu yazıda EventEmitter'ın temel metotlarını, 'error' olayının özel davranışını, dinleyici sınırı uyarısını, async dinleyicilerdeki tuzağı ve events.once() / events.on() ile Promise tabanlı kullanımı Node.js v26.13.0 üzerinde çalıştırılmış örneklerle anlatıyoruz. Serinin tamamı için Node.js öğrenme rehberi sayfasına bakabilirsiniz.
EventEmitter nedir, nereden gelir?
EventEmitter bir sınıftır ve iki biçimde kullanılır: doğrudan new EventEmitter() ile bir nesne oluşturursunuz ya da kendi sınıfınızı class Kuyruk extends EventEmitter şeklinde ondan türetirsiniz. Belgelere göre olay yayan bütün Node.js nesneleri bu sınıfın örnekleridir. Olay adları genellikle camelCase yazılmış string'lerdir; ancak geçerli herhangi bir özellik anahtarı, örneğin bir Symbol, de olay adı olabilir.
ESM'de import { EventEmitter } from 'node:events', CommonJS'te const EventEmitter = require('node:events') yazarsınız. CommonJS'te modülün kendisi sınıftır; ESM'de hem varsayılan dışa aktarım hem de adlandırılmış EventEmitter kullanılabilir. İki modül sisteminin farkları için CommonJS ve ESM karşılaştırmasına göz atabilirsiniz. Modül, Node.js belgelerinde Stability: 2 - Stable olarak işaretlidir.
on() ve emit(): dinleyici eklemek ve olay yaymak
emitter.on(olayAdi, dinleyici) dinleyiciyi o olayın listesinin sonuna ekler ve emitter'ın kendisini döndürür, bu yüzden çağrılar zincirlenebilir. emitter.emit(olayAdi, ...arguman) ise o olaya kayıtlı tüm dinleyicileri sırayla çağırır, verdiğiniz argümanları her birine iletir ve olayın en az bir dinleyicisi varsa true, yoksa false döndürür. Dinleyicilerin döndürdüğü değerler yok sayılır.
import { EventEmitter } from 'node:events';
const siparis = new EventEmitter();
siparis.on('olusturuldu', (id, tutar) => {
console.log(`Sipariş ${id} oluşturuldu, tutar: ${tutar} TL`);
});
siparis.on('olusturuldu', function (id) {
console.log('ikinci dinleyici, this === siparis:', this === siparis);
});
console.log('emit öncesi');
const vardi = siparis.emit('olusturuldu', 1001, 250);
console.log('emit sonrası, dinleyici var mıydı:', vardi);
console.log('olmayan olay:', siparis.emit('iptal'));
// çıktı: emit öncesi
// çıktı: Sipariş 1001 oluşturuldu, tutar: 250 TL
// çıktı: ikinci dinleyici, this === siparis: true
// çıktı: emit sonrası, dinleyici var mıydı: true
// çıktı: olmayan olay: falseÇıktının sırası önemli: emit sonrası satırı iki dinleyici de bittikten sonra yazıldı. emit() bir kuyruğa iş bırakıp hemen dönmez; dinleyicileri o anda, aynı çağrı yığınında çalıştırır. Belgeler bunun olayların doğru sırayla işlenmesini sağladığını ve yarış durumlarını önlediğini belirtir. Bir dinleyicinin işini sonraya bırakması gerekiyorsa içinde setImmediate() veya process.nextTick() kullanabilir; bu iki fonksiyonun farkını process.nextTick ve setImmediate yazısında ayrıntılı anlatıyoruz.
İkinci dinleyici normal function olarak yazıldığı için içindeki this, dinleyicinin bağlı olduğu emitter'ı gösterir. Ok fonksiyonu (arrow function) kullanırsanız bu bağlama yapılmaz; this çevreleyen kapsamdan gelir. ESM modülünün en üst düzeyinde bu değer undefined'dır. this kurallarının genel anlatımı için this anahtar kelimesi yazısına bakın.
once(), prependListener() ve dinleyici listesini okumak

once() ile eklenen dinleyici, olay ilk kez yayıldığında önce listeden çıkarılır, sonra çağrılır; ikinci emit() onu artık görmez. Varsayılan olarak dinleyiciler eklenme sırasıyla çalışır. Bir dinleyiciyi listenin başına koymak isterseniz prependListener(), tek seferlik bir dinleyiciyi başa koymak isterseniz prependOnceListener() kullanırsınız.
import { EventEmitter } from 'node:events';
const ee = new EventEmitter();
let n = 0;
ee.once('baglandi', () => console.log('once çalıştı', ++n));
ee.on('veri', () => console.log('a'));
ee.prependListener('veri', () => console.log('b (başa eklendi)'));
ee.emit('baglandi');
ee.emit('baglandi');
ee.emit('veri');
console.log(ee.listenerCount('baglandi'), ee.listenerCount('veri'));
console.log(ee.eventNames());
// çıktı: once çalıştı 1
// çıktı: b (başa eklendi)
// çıktı: a
// çıktı: 0 2
// çıktı: [ 'veri' ]listenerCount(olay) kayıtlı dinleyici sayısını, eventNames() dinleyicisi olan olay adlarını, listeners(olay) ise dinleyici dizisinin bir kopyasını verir. rawListeners() da kopya döndürür ama once() tarafından oluşturulan sarmalayıcı fonksiyonları da içerir. Bu metotlar özellikle hata ayıklarken ve testlerde, bir dinleyicinin gerçekten kaldırılıp kaldırılmadığını görmek için işe yarar.
| Metot | Ne yapar | Dönüş |
|---|---|---|
| on / addListener | Dinleyiciyi listenin sonuna ekler | emitter |
| once | Bir kez çalışacak dinleyici ekler | emitter |
| prependListener | Dinleyiciyi listenin başına ekler | emitter |
| off / removeListener | Dinleyicinin en fazla bir kopyasını çıkarır | emitter |
| removeAllListeners | Tüm dinleyicileri ya da bir olayınkileri siler | emitter |
| emit | Dinleyicileri senkron çağırır | true veya false |
| listenerCount | Dinleyici sayısı (isteğe bağlı belirli fonksiyon için) | sayı |
| eventNames | Dinleyicisi olan olay adları | dizi |
Dinleyici kaldırmak: off() ve removeListener()

off(), removeListener()'ın takma adıdır. Kaldırmak için dinleyiciye referansınız olmalıdır; yani dinleyiciyi isimli bir değişkende tutmanız gerekir. Satır içinde yazılmış anonim bir ok fonksiyonunu sonradan kaldıramazsınız. Belgelerde belirtilen iki ayrıntı sık karıştırılır. Birincisi, on() aynı fonksiyonun daha önce eklenip eklenmediğini kontrol etmez; aynı fonksiyonu iki kez eklerseniz iki kez çağrılır ve removeListener() her çağrıda yalnızca bir kopyayı çıkarır. İkincisi, bir emit() başladığında o anki dinleyici listesi sabitlenir; bir dinleyicinin o sırada başka bir dinleyiciyi kaldırması, devam eden yayını etkilemez.
import { EventEmitter } from 'node:events';
const ee = new EventEmitter();
const kaydet = () => console.log('kaydet');
const bildir = () => {
console.log('bildir');
ee.off('kayit', kaydet2);
};
const kaydet2 = () => console.log('kaydet2');
ee.on('kayit', kaydet);
ee.on('kayit', bildir);
ee.on('kayit', kaydet2);
ee.emit('kayit');
console.log('---');
ee.emit('kayit');
console.log('---');
ee.on('kayit', kaydet);
console.log('kaydet kaç kez kayıtlı:', ee.listenerCount('kayit', kaydet));
ee.removeListener('kayit', kaydet);
console.log('bir off sonrası:', ee.listenerCount('kayit', kaydet));
// çıktı: kaydet
// çıktı: bildir
// çıktı: kaydet2
// çıktı: ---
// çıktı: kaydet
// çıktı: bildir
// çıktı: ---
// çıktı: kaydet kaç kez kayıtlı: 2
// çıktı: bir off sonrası: 1İlk yayında bildir, kaydet2'yi kaldırdığı hâlde kaydet2 yine çalıştı; çünkü liste yayın başında kopyalanmıştı. İkinci yayında artık listede değil. listenerCount()'a ikinci argüman olarak fonksiyon verdiğinizde, o fonksiyonun listede kaç kez bulunduğunu sayar. removeAllListeners() için belgelerdeki uyarı açıktır: başka bir modülün oluşturduğu emitter'lardan (soketler, dosya akışları gibi) sizin eklemediğiniz dinleyicileri silmek kötü bir uygulamadır.
'error' olayı: dinleyici yoksa süreç kapanır
'error', EventEmitter için özel bir olay adıdır. Bir emitter'da hata olduğunda beklenen davranış 'error' olayını yaymaktır. Bu olay için kayıtlı hiç dinleyici yoksa Node.js hatayı fırlatır, yığın izini (stack trace) yazar ve süreç sonlanır. Aşağıdaki betik bu yüzden son satıra hiç ulaşmaz:
import { EventEmitter } from 'node:events';
const ee = new EventEmitter();
ee.emit('error', new Error('bağlantı koptu'));
console.log('bu satır hiç çalışmaz');
// çıktı (stderr, yollar kısaltıldı, çıkış kodu 1):
// file:///home/kullanici/proje/ev3.mjs:4
// ee.emit('error', new Error('bağlantı koptu'));
// ^
//
// Error: bağlantı koptu
// at file:///home/kullanici/proje/ev3.mjs:4:18
// ...
// Emitted 'error' event at:
// at file:///home/kullanici/proje/ev3.mjs:4:4
// ...
//
// Node.js v26.13.0Belgelerin önerdiği en iyi uygulama, hata yayabilecek her emitter'a bir 'error' dinleyicisi eklemektir. Yalnızca hataları kaydetmek, yani tüketmeden izlemek istiyorsanız events.errorMonitor sembolüyle dinleyici ekleyebilirsiniz. Bu dinleyiciler normal 'error' dinleyicilerinden önce çağrılır, ancak davranışı değiştirmez: başka bir 'error' dinleyicisi yoksa süreç yine kapanır.
import { EventEmitter, errorMonitor } from 'node:events';
const ee = new EventEmitter();
ee.on(errorMonitor, (err) => console.log('izleme:', err.message));
ee.on('error', (err) => console.log('işlendi:', err.message));
ee.emit('error', new Error('disk dolu'));
console.log('süreç çalışmaya devam ediyor');
// çıktı: izleme: disk dolu
// çıktı: işlendi: disk dolu
// çıktı: süreç çalışmaya devam ediyorAsync dinleyiciler ve captureRejections
Dinleyiciye async fonksiyon vermek mümkündür, ama emit() dönen Promise'i beklemez ve onunla ilgilenmez. Async dinleyici içinde fırlatılan hata reddedilmiş bir Promise'e dönüşür ve bu Promise'i kimse yakalamadığı için işlenmemiş ret (unhandled rejection) oluşur. Belgeler bu durumu açıkça sorunlu olarak niteler. Async fonksiyonların genel davranışı için async/await yazısına bakabilirsiniz.
Çözüm captureRejections seçeneğidir. Yapıcıya { captureRejections: true } verdiğinizde Node.js dinleyicinin döndürdüğü Promise'e bir ret işleyicisi bağlar ve hatayı asenkron olarak emitter'ın Symbol.for('nodejs.rejection') metoduna, bu metot yoksa 'error' olayına yönlendirir. EventEmitter.captureRejections = true ise bundan sonra oluşturulan tüm emitter'ların varsayılanını değiştirir.
import { EventEmitter } from 'node:events';
const ee = new EventEmitter({ captureRejections: true });
ee.on('kaydet', async () => {
throw new Error('veritabanı yanıt vermedi');
});
ee.on('error', (err) => console.log('error olayına geldi:', err.message));
ee.emit('kaydet');
console.log('emit döndü');
// çıktı: emit döndü
// çıktı: error olayına geldi: veritabanı yanıt vermediÇıktıda emit döndü önce geliyor; hata 'error' olayına asenkron olarak iletiliyor. Belgelerdeki bir uyarıyı da not edin: captureRejections ile üretilen 'error' olaylarına, sonsuz hata döngüsünü önlemek için ayrıca bir yakalama işleyicisi eklenmez. Bu yüzden 'error' dinleyicisinin kendisini async yazmamanız önerilir.
Dinleyici sınırı ve MaxListenersExceededWarning
Varsayılan olarak bir olaya 10'dan fazla dinleyici eklendiğinde Node.js stderr'e bir uyarı yazar. Bu katı bir sınır değildir; dinleyici yine eklenir. Amaç, örneğin her istekte aynı emitter'a yeni dinleyici ekleyip hiç kaldırmamak gibi bellek sızıntılarını erken fark ettirmektir. Uyarı process.on('warning') ile yakalanabilir; adı MaxListenersExceededWarning'dir ve emitter, type, count alanlarını taşır.
import { EventEmitter } from 'node:events';
process.on('warning', (w) => {
console.log(w.name, '| olay:', w.type, '| sayı:', w.count);
});
const ee = new EventEmitter();
console.log('varsayılan sınır:', ee.getMaxListeners());
for (let i = 0; i < 11; i++) {
ee.on('istek', () => {});
}
console.log('eklenen dinleyici:', ee.listenerCount('istek'));
// çıktı: varsayılan sınır: 10
// çıktı: eklenen dinleyici: 11
// çıktı: MaxListenersExceededWarning | olay: istek | sayı: 11
// stderr: (node:96604) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 istek listeners added to [EventEmitter]. MaxListeners is 10. Use emitter.setMaxListeners() to increase limit
// stderr: (Use `node --trace-warnings ...` to show where the warning was created)Uyarı gerçekten çok sayıda dinleyiciye ihtiyaç duyduğunuz meşru bir durumdan geliyorsa emitter.setMaxListeners(n) ile o nesnenin sınırını yükseltebilirsiniz; Infinity veya 0 sınırsız anlamına gelir. events.defaultMaxListeners değiştirildiğinde bu, değişiklikten önce oluşturulanlar dahil tüm emitter'ları etkiler, bu yüzden dikkatli kullanılmalıdır; nesne bazında setMaxListeners() her zaman önceliklidir. Uyarının kaynağını bulmak için --trace-warnings bayrağıyla çalıştırmak yığın izini gösterir. Sınırı yükseltmeden önce sızıntı olup olmadığını kontrol edin; bellek sızıntısı analizi için Node.js performans optimizasyonu yazısına bakabilirsiniz.
events.once() ve events.on(): Promise ve async iterator ile olay beklemek
node:events modülü, emitter'ları Promise dünyasına bağlayan iki yardımcı fonksiyon sunar. events.once(emitter, ad), olay yayıldığında çözülen bir Promise döndürür; değeri, olaya verilen tüm argümanlardan oluşan bir dizidir. Beklerken emitter 'error' yayarsa Promise reddedilir. events.on(emitter, ad) ise olayları sırayla veren bir async iterator döndürür; for await...of ile dolaşılır, 'error' gelirse hata fırlatır ve döngüden çıkıldığında eklediği dinleyicileri temizler.
import { EventEmitter, once, on } from 'node:events';
const ee = new EventEmitter();
setTimeout(() => ee.emit('hazir', 'v1', 42), 10);
const degerler = await once(ee, 'hazir');
console.log('once:', degerler);
setTimeout(() => {
ee.emit('satir', 'birinci');
ee.emit('satir', 'ikinci');
ee.emit('bitti');
}, 10);
for await (const [satir] of on(ee, 'satir', { close: ['bitti'] })) {
console.log('satır:', satir);
}
console.log('döngü bitti, kalan dinleyici:', ee.listenerCount('satir'));
// çıktı: once: [ 'v1', 42 ]
// çıktı: satır: birinci
// çıktı: satır: ikinci
// çıktı: döngü bitti, kalan dinleyici: 0close seçeneği yinelemeyi bitirecek olay adlarını alır; bu örnekte 'bitti' gelince döngü kapanır. close vermezseniz döngü siz break yapana ya da bir AbortSignal iptal edene kadar bekler. İki fonksiyon da signal seçeneğini kabul eder; sinyal iptal edilirse bekleme AbortError ile sonlanır. Bu konuyu AbortController yazısında ayrıca işliyoruz. Async iterator ve for await sözdiziminin kendisi için generator ve iterator yazısına göz atın.
import { EventEmitter, once } from 'node:events';
const ee = new EventEmitter();
const ac = new AbortController();
setTimeout(() => ac.abort(), 20);
try {
await once(ee, 'asla', { signal: ac.signal });
} catch (err) {
console.log(err.name, err.code);
}
setTimeout(() => ee.emit('error', new Error('patladı')), 5);
try {
await once(ee, 'veri');
} catch (err) {
console.log('once reddedildi:', err.message);
}
// çıktı: AbortError ABORT_ERR
// çıktı: once reddedildi: patladıMini örnek: EventEmitter'dan türeyen bir iş kuyruğu
Gerçek projelerde EventEmitter genellikle bir sınıfın temel sınıfı olarak kullanılır. Aşağıdaki Kuyruk sınıfı her eklemede 'eklendi', üç işe ulaşınca 'doldu' olayını yayar. ekle() metodu this döndürdüğü için çağrılar zincirlenebiliyor; 'doldu' dinleyicisi once() ile eklendiği için dördüncü eklemede tekrar çalışmıyor. Sınıf sözdizimi ve özel alanlar (#isler) için JavaScript sınıflar yazısına bakın.
import { EventEmitter } from 'node:events';
class Kuyruk extends EventEmitter {
#isler = [];
ekle(is) {
this.#isler.push(is);
this.emit('eklendi', is, this.#isler.length);
if (this.#isler.length >= 3) this.emit('doldu');
return this;
}
}
const k = new Kuyruk();
k.on('eklendi', (is, uzunluk) => console.log(`+ ${is} (toplam ${uzunluk})`));
k.once('doldu', () => console.log('kuyruk doldu, işleme başla'));
k.ekle('fatura').ekle('e-posta').ekle('rapor').ekle('yedek');
console.log(k instanceof EventEmitter);
// çıktı: + fatura (toplam 1)
// çıktı: + e-posta (toplam 2)
// çıktı: + rapor (toplam 3)
// çıktı: kuyruk doldu, işleme başla
// çıktı: + yedek (toplam 4)
// çıktı: trueBu sınıfta olaylar ekle() çağrıldığında yayıldığı için, dinleyiciler nesne oluşturulduktan sonra eklenebiliyor. Olayı yapıcının (constructor) içinden yayarsanız henüz kimse dinlemiyor olur ve olay kaybolur. Bu durumun çözümü olan process.nextTick() desenini nextTick ve setImmediate yazısında çalışan bir örnekle gösteriyoruz.
EventEmitter ile EventTarget farkı

Node.js, web standardı olan EventTarget ve Event sınıflarını da global olarak sunar; AbortSignal gibi bazı API'ler bu modeli kullanır. Belgelere göre Node.js'teki EventTarget'ta tarayıcıdaki gibi bir hiyerarşi ve olay yayılımı (propagation) yoktur. En önemli davranış farkları şöyle özetlenebilir:
| Özellik | EventEmitter | EventTarget |
|---|---|---|
| Dinleyici ekleme | on, once, prependListener | addEventListener (once seçeneğiyle) |
| Aynı fonksiyonu iki kez eklemek | İki kez eklenir ve çağrılır | NodeEventTarget için yok sayılır |
| error olayı | Dinleyici yoksa fırlatılır, süreç kapanır | Özel davranış yok |
| Dinleyici hata fırlatırsa | Hata emit çağrısına yayılır, sonraki dinleyiciler çalışmaz | Diğer dinleyiciler yine çalışır, hata yakalanmamış istisna olur |
| Async dinleyici reddi | captureRejections ile yakalanır | Otomatik yakalanır |
| Dinleyici sayısını görmek | listenerCount metodu | events.listenerCount veya getEventListeners |
Sık yapılan hatalar
- 'error' dinleyicisi eklememek: Dinleyicisiz bir
'error'olayı süreci kapatır. Kendi emitter'larınızda ve stream, soket gibi çekirdek nesnelerde mutlaka'error'dinleyin. - emit()'in asenkron olduğunu sanmak: Dinleyiciler senkron çalışır; uzun süren bir dinleyici
emit()'i çağıran kodu da bekletir. Ağır işleri dinleyicinin içinde sonraya bırakın. - Anonim dinleyiciyi kaldırmaya çalışmak:
off()fonksiyon referansı ister. Satır içinde yazdığınız ok fonksiyonunun referansı yoktur. - Her istekte dinleyici ekleyip kaldırmamak: Uzun yaşayan bir emitter'a her HTTP isteğinde
on()eklemek,MaxListenersExceededWarningile kendini gösteren bir sızıntıdır.once()kullanın ya da işi bitinceoff()çağırın. - Async dinleyicideki hatayı unutmak:
captureRejectionsolmadan async dinleyicinin fırlattığı hata işlenmemiş ret olur. Seçeneği açın ya da dinleyicinin içindetry/catchkullanın. - Uyarıyı susturmak için sınırı Infinity yapmak:
setMaxListeners(Infinity)uyarıyı kaldırır ama sızıntıyı gidermez. Önce dinleyicilerin neden biriktiğini bulun.
Sık Sorulan Sorular
EventEmitter senkron mu çalışır?
Evet. emit() çağrıldığında o olaya kayıtlı bütün dinleyiciler kayıt sırasıyla ve senkron olarak çağrılır; emit() ancak hepsi bitince döner. Asenkronluk ancak dinleyicinin içinde setImmediate(), bir zamanlayıcı ya da asenkron bir I/O işlemi başlatmasıyla gelir.
on() ile addListener() arasında fark var mı?
Hayır. Belgelere göre addListener(), on()'un takma adıdır. Benzer şekilde off() da removeListener()'ın takma adıdır. Hangisini kullandığınız yalnızca okunabilirlik tercihidir.
'error' olayını dinlemezsem ne olur?
Emitter 'error' yaydığında hiç dinleyici yoksa hata nesnesi fırlatılır, yığın izi stderr'e yazılır ve süreç sonlanır. errorMonitor ile eklenen izleme dinleyicileri bunu engellemez; süreci korumak için normal bir 'error' dinleyicisi gerekir.
MaxListenersExceededWarning uyarısını nasıl düzeltirim?
Önce gerçekten 10'dan fazla dinleyiciye ihtiyacınız olup olmadığına bakın; çoğu zaman uyarı, kaldırılmayan dinleyicilerden kaynaklanır. Meşru bir ihtiyaçsa ilgili nesnede setMaxListeners(n) ile sınırı yükseltin. Kaynağı bulmak için uygulamayı --trace-warnings ile çalıştırın; CLI bayrakları için NODE_OPTIONS ve CLI bayrakları yazısına bakabilirsiniz.
Bir olayı await ile bekleyebilir miyim?
Evet, node:events modülündeki once() fonksiyonu tam bunun içindir: const [deger] = await once(emitter, 'olay'). Olay akışını sırayla işlemek için for await (const args of on(emitter, 'olay')) kullanabilirsiniz.
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 · process.nextTick ve setImmediate farkı · AbortController ile iptal · Node.js stream nedir · uncaughtException ve unhandledRejection · Node.js nedir