Takip et

Event Emitter Nedir ve Neden Kullanılır?

Node.js, asenkron ve olay tabanlı mimarisiyle bilinen güçlü bir çalışma zamanı ortamıdır. Bu mimarinin temel taşlarından biri de Event E

Node.js, asenkron ve olay tabanlı mimarisiyle bilinen güçlü bir çalışma zamanı ortamıdır. Bu mimarinin temel taşlarından biri de Event Emitterlardır. Event Emitterlar, Node.js uygulamalarında modüller arası iletişimi sağlamanın, durum değişikliklerini yönetmenin ve uygulamanın farklı bölümlerini birbirinden bağımsız hale getirmenin zarif bir yolunu sunar. Bu rehberde, Event Emitterların ne olduğunu, nasıl kullanıldığını, en iyi uygulamaları ve pratik senaryolarını detaylı bir şekilde inceleyeceğiz.

Event Emitter Nedir ve Neden Kullanılır?

Event Emitterlar, “yayınla-abone ol” (publish-subscribe) deseninin Node.js’teki temel uygulamasıdır. Bu desen, bir olayın gerçekleştiğini duyuran bir “yayıncı” (emitter) ve bu olayı dinleyerek belirli bir eylemi gerçekleştiren bir veya daha fazla “abone” (listener) arasında gevşek bir bağlantı kurar.

Neden Kullanmalıyız?

  • Modülerlik ve Ayırma (Decoupling): Uygulamanızın farklı parçalarının doğrudan birbirini çağırmak yerine olaylar aracılığıyla iletişim kurmasını sağlar. Bu, kodu daha temiz, daha anlaşılır ve bakımı daha kolay hale getirir.
  • Esneklik: Yeni olay dinleyicileri eklemek veya mevcut olanları kaldırmak, yayıncı kodunu değiştirmeye gerek kalmadan kolayca yapılabilir.
  • Asenkron İşleme: Node.js’in asenkron doğasına mükemmel uyum sağlar. Olaylar genellikle arka planda gerçekleşen işlemleri tetiklemek için kullanılır.
  • Durum Yönetimi: Bir nesnenin veya sistemin durumundaki değişiklikleri diğer bileşenlere bildirmek için idealdir. Örneğin, bir kullanıcının kaydolması, bir dosyanın yüklenmesi veya bir veritabanı işleminin tamamlanması gibi olayları yayınlayabilirsiniz.
  • Node.js Çekirdeği: HTTP sunucuları, dosya sistemi işlemleri, stream’ler gibi Node.js’in birçok dahili modülü Event Emitter desenini yoğun bir şekilde kullanır. Bu, sizin de kendi uygulamanızda aynı deseni benimsemenizi kolaylaştırır.

Event Emitterlar, Node.js’in events modülü tarafından sağlanır.

Event Emitter Temelleri: Oluşturma, Dinleme ve Yayınlama

Event Emitter kullanmaya başlamak oldukça basittir. İşte temel adımlar:

1. Event Emitter Oluşturma

Kendi Event Emitter’ınızı iki ana yolla oluşturabilirsiniz:

  1. Doğrudan EventEmitter sınıfının bir örneğini oluşturarak:
  2. 
    const EventEmitter = require('events');
    const myEmitter = new EventEmitter();
    
  3. Kendi sınıfınızı EventEmitter‘dan miras alarak (tercih edilen yöntem, daha düzenli bir yapı sağlar):
  4. 
    const EventEmitter = require('events');
    
    class MyCustomEmitter extends EventEmitter {
      constructor() {
        super(); // EventEmitter constructor'ını çağırır
      }
    }
    
    const myCustomEmitter = new MyCustomEmitter();
    

2. Olayları Dinleme (Subscribing)

Bir olayı dinlemek için on() veya addListener() metodunu kullanırız. Bu metotlar iki argüman alır: dinlenecek olayın adı (string) ve olay gerçekleştiğinde çalıştırılacak bir callback fonksiyonu.


const EventEmitter = require('events');
const myEmitter = new EventEmitter();

// 'selam' olayını dinle
myEmitter.on('selam', () => {
  console.log('Merhaba Dünya!');
});

// Olay dinleyicisine argüman geçirme
myEmitter.on('kullaniciKaydoldu', (kullaniciAdi, email) => {
  console.log(Yeni kullanıcı kaydoldu: ${kullaniciAdi} (${email}));
});

3. Olayları Yayınlama (Emitting)

Bir olayı tetiklemek veya yayınlamak için emit() metodunu kullanırız. emit() metodu ilk argüman olarak olayın adını alır ve sonraki tüm argümanlar, olay dinleyicilerine (callback fonksiyonlarına) iletilir.


const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.on('selam', () => {
  console.log('Merhaba Dünya!');
});

myEmitter.on('kullaniciKaydoldu', (kullaniciAdi, email) => {
  console.log(Yeni kullanıcı kaydoldu: ${kullaniciAdi} (${email}));
});

// 'selam' olayını yayınla
myEmitter.emit('selam'); // Çıktı: Merhaba Dünya!

// 'kullaniciKaydoldu' olayını argümanlarla yayınla
myEmitter.emit('kullaniciKaydoldu', 'Ali Veli', 'ali.veli@example.com'); 
// Çıktı: Yeni kullanıcı kaydoldu: Ali Veli (ali.veli@example.com)

// Dinleyicisi olmayan bir olayı yayınlamak hiçbir şey yapmaz
myEmitter.emit('bilinmeyenOlay'); 

Önemli Not: Olaylar, dinleyiciler eklenmeden önce yayınlanırsa, bu olaylar kaçırılır. Dinleyicilerin her zaman olay yayınlanmadan önce kaydedilmesi gerekir.

Gelişmiş Kullanım ve En İyi Uygulamalar

Event Emitter’ların temel kullanımını anladıktan sonra, daha güçlü ve güvenli uygulamalar geliştirmek için bazı gelişmiş özelliklere ve en iyi uygulamalara göz atalım.

1. Tek Seferlik Dinleyiciler: once()

Bazen bir olayın yalnızca bir kez dinlenmesini ve ardından dinleyicinin kaldırılmasını istersiniz. Bunun için once() metodunu kullanabilirsiniz.


const EventEmitter = require('events');
const myEmitter = new EventEmitter();

let sayac = 0;
myEmitter.once('veriGeldi', () => {
  sayac++;
  console.log(Veri ilk kez geldi. Sayaç: ${sayac});
});

myEmitter.emit('veriGeldi'); // Çıktı: Veri ilk kez geldi. Sayaç: 1
myEmitter.emit('veriGeldi'); // Hiçbir şey olmaz, dinleyici kaldırıldı
myEmitter.emit('veriGeldi'); // Hala hiçbir şey olmaz

2. Dinleyicileri Kaldırma: removeListener() ve removeAllListeners()

Bellek sızıntılarını önlemek ve gereksiz işlem yükünü azaltmak için artık ihtiyaç duyulmayan olay dinleyicilerini kaldırmak önemlidir. Özellikle uzun ömürlü nesnelerde veya dinamik olarak eklenen/kaldırılan bileşenlerde bu hayati öneme sahiptir.

  • removeListener(eventName, listenerFunction) veya off(eventName, listenerFunction): Belirli bir olay için belirli bir dinleyici fonksiyonunu kaldırır. Fonksiyonun referansını iletmeniz gerekir.
  • removeAllListeners([eventName]): Belirli bir olay için tüm dinleyicileri kaldırır. Eğer eventName belirtilmezse, emitter üzerindeki tüm olaylar için tüm dinleyicileri kaldırır.

const EventEmitter = require('events');
const myEmitter = new EventEmitter();

function mesajDinleyici() {
  console.log('Bir mesaj geldi!');
}

function uyariDinleyici() {
  console.log('Bir uyarı geldi!');
}

myEmitter.on('mesaj', mesajDinleyici);
myEmitter.on('mesaj', () => console.log('Başka bir mesaj dinleyicisi.'));
myEmitter.on('uyari', uyariDinleyici);

console.log('Mesaj dinleyici sayısı:', myEmitter.listenerCount('mesaj')); // Çıktı: 2

myEmitter.emit('mesaj'); // İki mesaj dinleyicisi de çalışır

myEmitter.removeListener('mesaj', mesajDinleyici); // İlk dinleyiciyi kaldır

console.log('Mesaj dinleyici sayısı (kaldırma sonrası):', myEmitter.listenerCount('mesaj')); // Çıktı: 1

myEmitter.emit('mesaj'); // Sadece ikinci mesaj dinleyicisi çalışır

myEmitter.removeAllListeners('uyari'); // 'uyari' olayının tüm dinleyicilerini kaldır
console.log('Uyarı dinleyici sayısı (kaldırma sonrası):', myEmitter.listenerCount('uyari')); // Çıktı: 0

myEmitter.removeAllListeners(); // Tüm olayların tüm dinleyicilerini kaldır
console.log('Tüm dinleyiciler kaldırıldı.');

3. Hata Olaylarını Yönetme: 'error'

Node.js’te, bir Event Emitter bir 'error' olayı yayınladığında ve bu olayın dinleyicisi yoksa, Node.js süreci varsayılan olarak çöker. Bu nedenle, hata olaylarını her zaman ele almak çok önemlidir.


const EventEmitter = require('events');
const myEmitter = new EventEmitter();

// Hata dinleyicisi eklenmezse uygulama çöker!
myEmitter.on('error', (err) => {
  console.error('Bir hata oluştu:', err.message);
  // Hatanın ciddiyetine göre burada uygun bir işlem yapılmalı (örn: loglama, uygulamayı kapatma)
});

// Hata olayını yayınla
myEmitter.emit('error', new Error('Beklenmedik bir durum oluştu!')); 
// Çıktı: Bir hata oluştu: Beklenmedik bir durum oluştu!

// Aşağıdaki kod, eğer yukarıda 'error' dinleyicisi olmasaydı uygulamayı çökerirdi:
// myEmitter.emit('error', new Error('Başka bir hata!'));

4. Maksimum Dinleyici Sayısı: setMaxListeners()

Varsayılan olarak, bir Event Emitter için belirli bir olaya ekleyebileceğiniz dinleyici sayısı 10 ile sınırlıdır. 10’dan fazla dinleyici eklerseniz, Node.js bir uyarı yayınlar (uygulamanız çökmez). Bu limit, bellek sızıntılarını veya performans sorunlarını işaret edebilecek potansiyel sorunları tespit etmek için bir güvenlik önlemidir. Bu limiti setMaxListeners() ile değiştirebilirsiniz:


const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.setMaxListeners(20); // Maksimum dinleyici sayısını 20 olarak ayarla

for (let i = 0; i < 15; i++) {
  myEmitter.on('data', () => console.log(Dinleyici ${i} veri aldı.));
}
// Bu durumda 15 dinleyici eklendiği için uyarı almayız.
// Eğer setMaxListeners() kullanmasaydık 11. dinleyiciden itibaren uyarı alırdık.

5. Asenkron vs. Senkron Çalışma

Event Emitterlar olayları senkron olarak yayınlar, yani emit() çağrıldığında, tüm dinleyiciler sırayla hemen çalıştırılır. Ancak, dinleyicinin içindeki kod asenkron olabilir.


const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.on('asyncEvent', () => {
  console.log('Dinleyici 1: Hemen çalıştı.');
  setTimeout(() => {
    console.log('Dinleyici 1: Asenkron işlem bitti.');
  }, 100);
});

myEmitter.on('asyncEvent', () => {
  console.log('Dinleyici 2: Hemen çalıştı.');
});

console.log('Olayı yayınlamadan önce.');
myEmitter.emit('asyncEvent');
console.log('Olayı yayınladıktan sonra.');

/* Çıktı sırası:
Olayı yayınlamadan önce.
Dinleyici 1: Hemen çalıştı.
Dinleyici 2: Hemen çalıştı.
Olayı yayınladıktan sonra.
Dinleyici 1: Asenkron işlem bitti.
*/

Gördüğünüz gibi, emit() çağrısı, tüm senkron dinleyiciler tamamlanana kadar bloke olur. Eğer bir olayın yayınlanmasının tamamen asenkron olmasını ve diğer kodun çalışmasını engellememesini istiyorsanız, process.nextTick() veya setImmediate() gibi mekanizmaları kullanabilirsiniz:


const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.on('nextTickEvent', () => {
  process.nextTick(() => {
    console.log('Dinleyici: nextTick ile asenkron çalıştı.');
  });
});

console.log('Olayı yayınlamadan önce.');
myEmitter.emit('nextTickEvent');
console.log('Olayı yayınladıktan sonra.');

/* Çıktı sırası:
Olayı yayınlamadan önce.
Olayı yayınladıktan sonra.
Dinleyici: nextTick ile asenkron çalıştı.
*/

Pratik Uygulama Senaryoları

Event Emitter’lar, Node.js uygulamalarında birçok farklı senaryoda kullanılabilir. İşte bazı örnekler:

1. Özel Modüller ve Servisler Arası İletişim

Kendi uygulamanızda farklı modüllerin veya servislerin birbirleriyle doğrudan bağımlılık olmadan iletişim kurmasını sağlayabilirsiniz.


// userManager.js
const EventEmitter = require('events');

class UserManager extends EventEmitter {
  constructor() {
    super();
  }

  kayitOl(kullaniciAdi, sifre) {
    // Kullanıcı kaydetme mantığı...
    console.log(${kullaniciAdi} adlı kullanıcı kaydediliyor...);
    // Kayıt başarılı olduğunda bir olay yayınla
    this.emit('userRegistered', { username: kullaniciAdi, timestamp: new Date() });
  }

  sifreSifirla(kullaniciAdi) {
    // Şifre sıfırlama mantığı...
    console.log(${kullaniciAdi} kullanıcısının şifresi sıfırlanıyor...);
    this.emit('passwordReset', { username: kullaniciAdi, timestamp: new Date() });
  }
}

module.exports = UserManager;

// app.js (Ana uygulama dosyası)
const UserManager = require('./userManager');
const userManager = new UserManager();

// Kullanıcı kayıt olayını dinle
userManager.on('userRegistered', (userData) => {
  console.log([LOG] Yeni kullanıcı kaydoldu: ${userData.username} - ${userData.timestamp});
  // E-posta gönderme, hoş geldin mesajı gibi işlemler yapılabilir
});

// Şifre sıfırlama olayını dinle
userManager.on('passwordReset', (userData) => {
  console.log([ALERT] ${userData.username} kullanıcısının şifresi sıfırlandı! - ${userData.timestamp});
  // Yöneticilere bildirim gönderme gibi işlemler yapılabilir
});

// Kullanıcı işlemleri
userManager.kayitOl('denemeKullanici', 'parola123');
userManager.sifreSifirla('denemeKullanici');

2. Uzun Süreli İşlemlerin Durumunu Bildirme

Bir dosya yükleme, veri işleme veya API çağrısı gibi uzun süren işlemlerin ilerlemesini veya tamamlanmasını bildirmek için Event Emitter kullanabilirsiniz.


const EventEmitter = require('events');

class DataProcessor extends EventEmitter {
  constructor() {
    super();
  }

  processData(data) {
    this.emit('processingStarted', data.length);
    console.log(Veri işleme başladı, ${data.length} öğe.);

    let processedCount = 0;
    for (let i = 0; i < data.length; i++) {
      // Yoğun işlem simülasyonu
      setTimeout(() => {
        processedCount++;
        this.emit('progress', processedCount / data.length * 100);
        if (processedCount === data.length) {
          this.emit('processingComplete', 'Tüm veri başarıyla işlendi.');
        }
      }, i * 10); // Her öğe için küçük bir gecikme
    }
  }
}

const processor = new DataProcessor();

processor.on('processingStarted', (totalItems) => {
  console.log(Toplam ${totalItems} öğe işlenecek.);
});

processor.on('progress', (percentage) => {
  console.log(İlerleme: ${percentage.toFixed(2)}%);
});

processor.on('processingComplete', (message) => {
  console.log(İşlem Bitti: ${message});
});

processor.on('error', (err) => {
  console.error('Veri işleme sırasında hata:', err.message);
});

// İşlenecek örnek veri
const myData = Array.from({ length: 20 }, (_, i) => item-${i + 1});
processor.processData(myData);

3. Durum Değişikliklerini İzleme

Bir nesnenin dahili durumundaki değişiklikleri dışarıya bildirmek için Event Emitter’lar kullanılabilir.


const EventEmitter = require('events');

class LightSwitch extends EventEmitter {
  constructor() {
    super();
    this.isOn = false;
  }

  toggle() {
    this.isOn = !this.isOn;
    if (this.isOn) {
      this.emit('turnedOn');
    } else {
      this.emit('turnedOff');
    }
    this.emit('stateChanged', this.isOn ? 'AÇIK' : 'KAPALI');
  }

  getState() {
    return this.isOn;
  }
}

const livingRoomLight = new LightSwitch();

livingRoomLight.on('turnedOn', () => {
  console.log('Oturma odası ışığı açıldı.');
});

livingRoomLight.on('turnedOff', () => {
  console.log('Oturma odası ışığı kapatıldı.');
});

livingRoomLight.on('stateChanged', (newState) => {
  console.log(Işığın yeni durumu: ${newState});
});

livingRoomLight.toggle(); // Işığı açar
livingRoomLight.toggle(); // Işığı kapatır
livingRoomLight.toggle(); // Işığı tekrar açar

Sonuç ve Sıkça Sorulan Sorular (SSS)

Event Emitter’lar, Node.js’in temel ve güçlü bir özelliğidir. Uygulamalarınızda modüller arası iletişimi düzenlemenize, olaylara dayalı mantık oluşturmanıza ve genel olarak daha ölçeklenebilir, bakımı kolay ve esnek bir kod tabanı oluşturmanıza yardımcı olurlar. Doğru kullanıldığında, karmaşık sistemlerin anlaşılırlığını artırabilir ve geliştirme sürecini hızlandırabilirler.

Sıkça Sorulan Sorular (SSS)

Event Emitter nedir?

Event Emitter, Node.js’te “yayınla-abone ol” (publish-subscribe) desenini uygulayan bir mekanizmadır. Bir olayın gerçekleştiğini duyurur (yayınlar) ve bu olaya abone olan (dinleyen) fonksiyonların çalıştırılmasını sağlar.

Neden Event Emitter kullanmalıyım?

Modüller arası bağımlılığı azaltır (decoupling), kodu daha modüler ve esnek hale getirir, asenkron işlemleri yönetmeye yardımcı olur ve uygulamanızın farklı bölümlerinin olaylar aracılığıyla iletişim kurmasını sağlar. Node.js’in birçok dahili modülü de bu deseni kullanır.

on() ve once() metotları arasındaki fark nedir?

on() metodu, bir olayı her yayınlandığında dinleyen kalıcı bir dinleyici ekler. once() metodu ise, bir olayı yalnızca bir kez dinleyen ve olay ilk kez yayınlandığında otomatik olarak kaldırılan bir dinleyici ekler.

Hata olaylarını nasıl ele alırım?

Bir Event Emitter üzerinde 'error' olayını dinleyerek hataları ele almalısınız. Eğer bir 'error' olayı yayınlanır ve bu olayı dinleyen hiçbir fonksiyon yoksa, Node.js süreci varsayılan olarak çöker. Bu nedenle her zaman bir 'error' dinleyicisi eklemek iyi bir uygulamadır.

Event Emitter’larda bellek sızıntılarını nasıl önlerim?

Artık ihtiyaç duyulmayan olay dinleyicilerini removeListener() veya removeAllListeners() metotları ile kaldırdığınızdan emin olun. Özellikle uzun ömürlü nesnelerde veya dinamik olarak eklenen/kaldırılan bileşenlerde bu önemlidir. Ayrıca, setMaxListeners() ile dinleyici sayısını kontrol altında tutarak olası sızıntıları erkenden fark edebilirsiniz.

Event Emitter senkron mu asenkron mu çalışır?

emit() çağrısı senkron olarak çalışır, yani olay yayınlandığında tüm dinleyiciler hemen sırayla çalıştırılır. Ancak, dinleyici fonksiyonlarının içinde asenkron işlemler (örneğin setTimeout, Promise’ler) olabilir. Eğer olayın yayınlanmasını tamamen asenkron hale getirmek isterseniz, dinleyici fonksiyonlarınızın içinde process.nextTick() veya setImmediate() kullanabilirsiniz.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.