events

Pola EventEmitter yang menjadi fondasi ekosistem Node.

Daftar isi

Modul events menyediakan kelas EventEmitter yang menjadi dasar pola berbasis peristiwa pada Node. Hampir seluruh modul inti yang menghasilkan data secara bertahap, seperti stream, http, dan net, dibangun di atas EventEmitter. Kelas ini memungkinkan komponen yang menghasilkan peristiwa untuk tidak peduli tentang siapa yang memprosesnya, sehingga kode tetap longgar coupling. Setelah membaca topik ini, pembaca dapat membuat objek yang memancarkan peristiwa, memasang penerima, serta memahami perbedaan antara on, once, dan penanganan peristiwa error.

Membuat dan memancarkan peristiwa#

Kelas EventEmitter dipakai dengan membuat instans lalu memasang penerima dengan metode on(namaPeristiwa, listener). Penerima adalah fungsi biasa yang dipanggil setiap kali metode emit(namaPeristiwa, ...args) dipanggil. Argumen yang diberikan pada emit setelah nama peristiwa diteruskan ke setiap penerima sebagai argumen posisional. Satu peristiwa dapat memiliki beberapa penerima yang semuanya dipanggil secara sinkron dalam urutan pendaftaran.

js events-demo.js
const { EventEmitter } = require('node:events');

const antrean = new EventEmitter();

antrean.on('tamu_datang', (nama) => {
  console.log(`selamat datang, ${nama}`);
});

antrean.once('tamu_datang', (nama) => {
  console.log(`(catatan: tamu pertama hari ini adalah ${nama})`);
});

antrean.emit('tamu_datang', 'ali');
antrean.emit('tamu_datang', 'budi');
antrean.emit('tamu_datang', 'citra');
text Keluaran
selamat datang, ali
(catatan: tamu pertama hari ini adalah ali)
selamat datang, budi
selamat datang, citra

Metode once memasang penerima yang dipanggil satu kali saja dan kemudian dihapus secara otomatis. Pada contoh di atas, penerima once hanya memproses peristiwa pertama dengan nama ali, sedangkan penerima on memproses seluruh tiga peristiwa. Urutan keluaran mencerminkan urutan pendaftaran: penerima on yang lebih dulu dipasang dipanggil lebih dulu, diikuti penerima once. Pemanggilan emit bersifat sinkron, sehingga seluruh penerima selesai sebelum baris berikutnya dijalankan.

Sebuah objek EventEmitter dapat memancarkan banyak nama peristiwa yang berbeda, dan setiap nama dianggap independen. Penerima yang dipasang untuk nama tamu_datang tidak dipanggil ketika nama tamu_pergi dipancarkan. Pemisahan ini menjadikan EventEmitter sarana penamaan kanal yang fleksibel: satu objek dapat menjadi pintu masuk beberapa jenis peristiwa tanpa saling mengganggu. Nama peristiwa biasanya berupa string, tetapi Symbol juga dapat dipakai untuk membuat peristiwa yang tidak terbaca dari luar dan tidak rentan bentrokan nama.

Menghapus penerima#

Penerima yang dipasang dengan on tetap aktif sampai dihapus atau sampai objek EventEmitter dihapus oleh pengumpul sampah. Penghapusan dilakukan dengan off(namaPeristiwa, listener) atau aliasnya removeListener, keduanya menerima referensi fungsi yang sama dengan yang dipasang. Metode removeAllListeners(namaPeristiwa) menghapus seluruh penerima untuk satu nama peristiwa, dan listenerCount(namaPeristiwa) mengembalikan jumlah penerima yang masih aktif.

js events-remove.js
const { EventEmitter } = require('node:events');

const sapa = new EventEmitter();

function halo(nama) {
  console.log(`halo, ${nama}`);
}

sapa.on('datang', halo);
sapa.emit('datang', 'dewi');
sapa.off('datang', halo);
sapa.emit('datang', 'evi');
console.log('jumlah penerima datang:', sapa.listenerCount('datang'));
text Keluaran
halo, dewi
jumlah penerima datang: 0

Penerima halo dipanggil sekali ketika datang dipancarkan dengan nama dewi, kemudian dihapus dengan off. Pemancaran kedua dengan nama evi tidak menghasilkan keluaran karena penerima sudah tidak ada. Pemeriksaan listenerCount mengonfirmasi bahwa jumlah penerima kembali menjadi nol. Fungsi anonim yang dipasang dengan on tidak dapat dihapus dengan off karena referensinya tidak disimpan, sehingga penerima yang perlu dihapus wajib didefinisikan sebagai fungsi bernama atau disimpan pada variabel terlebih dahulu.

Peristiwa error#

EventEmitter memperlakukan peristiwa error secara khusus. Bila emit('error', ...) dipanggil tanpa penerima yang terpasang untuk error, Node melemparkan error tersebut dan membuat proses berakhir dengan kode keluar bukan nol. Perilaku ini berbeda dari peristiwa biasa yang hanya diabaikan bila tidak ada penerima. Karena itu, penerima error wajib dipasang pada objek EventEmitter yang dapat memancarkan error, terutama untuk stream dan klien jaringan.

js events-error.js
const { EventEmitter } = require('node:events');

const saluran = new EventEmitter();

saluran.on('error', (err) => {
  console.log('tertangkap:', err.message);
});

saluran.emit('error', new Error('sambungan terputus'));
text Keluaran
tertangkap: sambungan terputus

Penerima error menerima objek Error sebagai argumen, dan penerima di atas memutuskan untuk mencatat pesannya alih-alih membiarkan error naik ke loop. Bila baris saluran.on('error', ...) dihapus, pemanggilan emit('error', ...) akan melempar error dan menghentikan proses. Pola yang lebih tangguh pada produksi adalah memakai events.on(emitter, namaPeristiwa) yang mengembalikan AsyncIterator sehingga penanganan error dapat dipindahkan ke blok try/catch pada async/await. Alternatif yang lebih sederhana adalah events.once(emitter, namaPeristiwa) yang mengembalikan promise dan cocok untuk menunggu satu peristiwa saja.

Penerima asinkron dan perambatan error#

Penerima yang dideklarasikan sebagai fungsi async dapat melempar error secara tidak terduga. Secara default, error yang dilempar oleh penerima asinkron tidak ditangkap oleh EventEmitter dan berakhir sebagai unhandled promise rejection, yaitu penolakan promise yang tidak ditangani penerima mana pun, yang pada Node versi baru dapat menghentikan proses. Opsi captureRejections: true pada konstruktor EventEmitter mengubah perilaku tersebut. Saat penerima asinkron menolak, Node memancarkan ulang error tersebut sebagai peristiwa error, sehingga error ditangkap oleh penerima error yang sudah dipasang. Pola ini menggabungkan kenyamanan async/await dengan keamanan penanganan error EventEmitter.

Untuk memantau seluruh peristiwa pada satu objek tanpa memasang penerima spesifik, EventEmitter menyediakan peristiwa newListener dan removeListener yang dipancarkan ketika penerima dipasang atau dihapus. Peristiwa ini berguna pada pembungkus (wrapper) yang ingin melacak pendaftaran penerima tanpa memodifikasi kode pemanggil.

Ringkasan#

  • EventEmitter.on memasang penerima yang dipanggil setiap kali peristiwa dipancarkan, sedangkan once memasang penerima yang dipanggil satu kali dan kemudian dihapus.
  • emit bersifat sinkron dan memanggil penerima dalam urutan pendaftaran, dengan argumen posisional yang diteruskan setelah nama peristiwa.
  • off memerlukan referensi fungsi yang sama dengan yang dipasang, sehingga penerima yang akan dihapus harus disimpan pada variabel atau didefinisikan sebagai fungsi bernama.
  • Peristiwa error tanpa penerima menyebabkan proses berakhir, sehingga penerima error wajib dipasang pada objek yang dapat memancarkan error.

Referensi#