Observer Pattern Pada Express JS
Pola penting dan dasar lainnya yang digunakan oleh Node.js adalah observer pattern. Bersama dengan reactor, callback, and modules, ini adalah salah satu pilar platform dan prasyarat mutlak untuk menggunakan banyak inti node dan modul userland.
Observer adalah solusi ideal untuk memodelkan sifat reaktif Node.js dan merupakan pelengkap sempurna untuk callback. Mari kita berikan definisi formal sebagai berikut:
CatatanPattern (observer): Mendefinisikan objek (disebut subjek). Objek (disebut subjek) dapat memberi tahu observer (atau listeners) ketika terjadi perubahan status.
Perbedaan utama dari callback pattern adalah bahwa subjek sebenarnya dapat memberi tahu banyak observers, tetapi callback gaya penerusan lanjutan biasanya menyebarkan hasilnya ke hanya satu listener, callback.
The EventEmitter
Dalam pemrograman berorientasi objek tradisional, observer membutuhkan interface, concreate clases, dan hierarki. Dengan Node.js, semuanya menjadi lebih mudah. Observer sudah dibangun ke dalam inti dan tersedia dari kelas EventEmitter. Anda dapat menggunakan kelas EventEmitter untuk mendaftarkan satu atau beberapa fungsi sebagai listeners. Listener dipanggil ketika tipe event tertentu terjadi. Gambar berikut secara visual menggambarkan konsep tersebut.
EventEmitter adalah prototipe, dan diekspor dari modul Event core. Kode berikut menunjukkan bagaimana kita bisa mendapatkan referensi untuk itu:
var EventEmitter = require('events').EventEmitter; var eeInstance = new EventEmitter();Metode dasar EventEmitter adalah:
- on (event, listener), Metode ini memungkinkan Anda untuk mendaftarkan listener (fungsi) baru untuk tipe event (string) yang ditentukan.
- once (Event, listener), Metode ini mendaftarkan listener baru. Listener ini akan dihapus setelah Event pertama kali dipublikasikan.
- Emmit (event, [arg1], […]), Metode ini membuat event baru dan memberikan argumen tambahan untuk diteruskan ke listener.
- removeListener (event, listener) Metode ini menghapus listener untuk tipe event yang ditentukan.
Semua metode di atas mengembalikan instance EventEmitter untuk memungkinkan chaining. Fungsi listener memiliki tanda tangan function([arg1], […]), jadi ia hanya menerima argumen saat Event dipicu. Di dalam listener, this merujuk ke instance EventEmitter yang menghasilkan event.
Kita sudah melihat bahwa ada perbedaan besar antara listener dan callback Node.js tradisional. Secara khusus, argumen pertama bukanlah kesalahan, tetapi bisa berupa data apa pun yang dikirimkan ke emit()pada saat panggilan.
Buat dan gunakan EventEmitter
Mari kita lihat bagaimana sebenarnya menggunakan EventEmitter. Cara termudah adalah membuat instance baru dan menggunakannya secara langsung. Kode berikut menunjukkan fungsi yang menggunakan EventEmitter untuk memberi tahu pelanggan secara real time ketika pola tertentu ditemukan dalam daftar file.
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
function findPattern(files, regex) {
var emitter = new EventEmitter();
files.forEach(function(file) {
fs.readFile(file, 'utf8', function(err, content) {
if(err)
return emitter.emit('error', err);
emitter.emit('fileread', file);
var match = null;
if(match = content.match(regex))
match.forEach(function(elem) {
emitter.emit('found', file, elem);
});
});
});
return emitter;
}
EventEmitter yang dibuat oleh fungsi di atas akan menghasilkan tiga event:
- fileread: Event ini dipicu ketika file dibaca
- found: Event ini terjadi ketika kecocokan ditemukan
- error: Event ini terjadi ketika kesalahan terjadi saat membaca file
Sekarang mari kita lihat bagaimana kita dapat menggunakan fungsi findPattern().
findPattern(
['fileA.txt', 'fileB.json'],
/hello \w+/g
)
.on('fileread', function(file) {
console.log(file + ' was read');
})
.on('found', function(file, match) {
console.log('Matched "' + match + '" in file ' + file);
})
.on('error', function(err) {
console.log('Error emitted: ' + err.message);
});
Pada contoh sebelumnya, kami mendaftarkan listener untuk masing-masing dari tiga jenis Event yang dihasilkan oleh EventEmitter yang dibuat oleh fungsi findPattern().
Propagasi errors
EventEmitter tidak dapat mengeluarkan pengecualian saat kondisi kesalahan terjadi, seperti dalam kasus callback. Hal ini karena jika Event terjadi secara asinkron, loop Event akan kehilangan pengecualian. Sebagai gantinya, biasanya mengeluarkan event khusus yang disebut error dan meneruskan objek Error sebagai argumen. Inilah tepatnya yang kita lakukan dengan fungsi findPattern() yang kita definisikan sebelumnya.
CatatanSebaiknya daftarkan listener Event kesalahan, karena Node.js menangani Event kesalahan dengan cara khusus dan secara otomatis mengeluarkan pengecualian dan keluar dari program jika listener terkait tidak ditemukan.
Jadikan objek dapat diamati
Membuat objek baru yang dapat ditonton langsung dari kelas EventEmitter mungkin tidak cukup. Ini karena tidak praktis untuk menyediakan lebih dari sekadar pembuatan Event baru. Faktanya, lebih umum untuk dapat memantau objek umum. Ini dimungkinkan dengan memperluas kelas EventEmitter.
Untuk mendemonstrasikan pola ini, mari implementasikan fungsionalitas fungsi findPattern() pada objek sebagai berikut:
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var fs = require('fs');
function FindPattern(regex) {
EventEmitter.call(this);
this.regex = regex;
this.files = [];
}
util.inherits(FindPattern, EventEmitter);
FindPattern.prototype.addFile = function(file) {
this.files.push(file);
return this;
};
FindPattern.prototype.find = function() {
var self = this;
self.files.forEach(function(file) {
fs.readFile(file, 'utf8', function(err, content) {
if(err)
return self.emit('error', err);
self.emit('fileread', file);
var match = null;
if(match = content.match(self.regex))
match.forEach(function(elem) {
self.emit('found', file, elem);
});
});
});
return this;
};
Prototipe DefinePattern yang telah Anda tetapkan memperluas EventEmitter menggunakan fungsi inherits() yang disediakan oleh util modul core. Dengan cara ini, ia menjadi kelas yang dapat diamati sepenuhnya. Berikut ini adalah contoh penggunaannya.
var findPatternObject = new FindPattern(/hello \w+/);
findPatternObject
.addFile('fileA.txt')
.addFile('fileB.json')
.find()
.on('found', function(file, match) {
console.log('Matched "' + match + '" in file ' + file);
})
.on('error', function(err) {
console.log('Error emitted ' + err.message);
});
Sekarang, selain dapat memantau dengan mewarisi fungsionalitas EventEmitter, Anda dapat melihat bagaimana objek FindPattern memiliki kumpulan metode yang lengkap.
Ini adalah pola yang sangat umum di ekosistem Node.js. Misalnya, objek Server dari modul http inti mendefinisikan metode seperti listen(), close(), setTimeout (), dan secara internal mewarisi dari fungsi EventEmitter juga. Oleh karena itu, Anda dapat membuat Event seperti resquest, saat permintaan baru received, atau connection. saat connection baru dibuat, closed, atau saat server ditutup.
Contoh penting lainnya dari objek yang memperluas EventEmitter adalah Node.js streams. Informasi lebih lanjut tentang streams diberikan dalam chapter 3, "Pengodean dengan streams".
Event Synchronous dan asynchronous
Seperti callback, Event dapat diaktifkan secara Synchronous atau asynchronous. Sangat penting untuk tidak mencampur dua pendekatan dengan EventEmitter yang sama, tetapi yang lebih penting, untuk menghindari masalah yang sama yang dijelaskan saat memublikasikan jenis Event yang sama. Terletak di bagian Melepas Zalgo.
Perbedaan utama antara memublikasikan Event Synchronous dan asynchronous adalah cara listener terdaftar. Jika Event dipicu secara asynchronous, Event tersebut dijamin tidak akan terjadi hingga siklus berikutnya dari loop Event, sehingga pengguna selalu dapat mendaftarkan listener baru, bahkan setelah EventEmitter diinisialisasi. Itulah tepatnya yang terjadi dengan fungsi findPattern(). Fungsi ini sebelumnya telah didefinisikan dan mewakili pendekatan umum yang digunakan oleh sebagian besar modul Node.js.
Sebaliknya, untuk menerbitkan Event secara Synchronous, semua listener harus terdaftar sebelum fungsi EventEmitter mulai menerbitkan Event. Mari kita lihat sebuah contoh:
function SyncEmit() {
this.emit('ready');
}
util.inherits(SyncEmit, EventEmitter);
var syncEmit = new SyncEmit();
syncEmit.on('ready', function() {
console.log('Object is ready to be used');
});
Jika Event siap dikeluarkan secara tidak Synchronous, kode sebelumnya berfungsi dengan baik. Namun, Event dihasilkan secara Synchronous dan listener terdaftar setelah Event telah dikirim, sehingga listener tidak pernah dipanggil sebagai hasilnya. Kode tidak menampilkan apa pun ke konsol.
Bertentangan dengan callback, ada situasi di mana masuk akal untuk menggunakan EventEmitter secara sinkron untuk tujuan yang berbeda. Untuk alasan ini, sangat penting untuk secara jelas menekankan perilaku EventEmitter dalam dokumentasi untuk menghindari kebingungan dan kemungkinan penyalahgunaan.
EventEmitter dan callback
Dilema umum saat mendefinisikan API asinkron adalah menggunakan EventEmitter atau pastikan Anda menerima callback. Aturan pembeda yang umum adalah semantik. Jika Anda perlu mengembalikan hasilnya secara asynchronous, Anda harus menggunakan callback. Sebaliknya, jika Anda perlu memberi tahu apa yang terjadi, Anda harus menggunakan event.
Tetapi selain prinsip sederhana ini, fakta bahwa kedua paradigma hampir selalu setara dan hasil yang sama dapat dicapai menciptakan banyak kebingungan. Sebagai contoh, perhatikan kode berikut.
function helloEvents() {
var eventEmitter = new EventEmitter();
setTimeout(function() {
eventEmitter.emit('hello', 'world');
}, 100);
return eventEmitter;
}
function helloCallback(callback) {
setTimeout(function() {
callback('hello', 'world');
}, 100);
}
Dua fungsi helloEvents() dan helloCallback() dapat dianggap setara secara fungsional. Yang pertama menggunakan Event untuk memberi tahu penyelesaian batas waktu, yang kedua menggunakan callback untuk memberi tahu pemanggil, meneruskan jenis Event sebagai argumen. Namun yang membedakannya adalah keterbacaan, semantik, dan jumlah kode yang perlu diimplementasikan atau digunakan. Kami tidak dapat memberikan seperangkat aturan deterministik untuk memilih salah satu gaya, tetapi kami pasti dapat memberikan beberapa tip untuk membantu Anda membuat keputusan.
Pengamatan pertama adalah bahwa ada beberapa batasan untuk callback dalam hal mendukung berbagai jenis Event. Bahkan, Anda dapat membedakan antara beberapa Event dengan meneruskan tipe sebagai argumen ke callback, atau dengan menerima beberapa callback, satu untuk setiap Event yang didukung. Namun, ini tidak dapat dianggap sebagai API yang elegan. Dalam situasi ini, EventEmitter dapat menyediakan antarmuka yang lebih baik dan kode yang lebih ramping.
Kasus lain di mana EventEmitter diinginkan adalah ketika Event yang sama dapat terjadi beberapa kali, atau tidak sama sekali. Bahkan, callback diharapkan dipanggil hanya sekali, terlepas dari apakah operasi itu berhasil. Mengingat fakta bahwa situasinya dapat berulang, kita perlu memikirkan kembali sifat semantik dari wabah tersebut. Ini lebih seperti Event yang perlu dikomunikasikan, bukan hasil. Dalam hal ini, EventEmitter direkomendasikan.
Terakhir, API yang menggunakan callback hanya dapat memberi tahu callback tertentu, tetapi fungsi EventEmitter dapat mengizinkan banyak listener untuk menerima notifikasi yang sama.
Gabungkan callback dengan EventEmitter
Dalam beberapa situasi, EventEmitter dapat digunakan dalam kombinasi dengan callback. Pola ini sangat berguna jika Anda ingin menerapkan prinsip pengurangan luas permukaan dengan mengekspor fungsi asinkron tradisional sebagai fungsi utama, tetapi tetap menyediakan lebih banyak fungsi dan lebih banyak lagi dengan mengembalikan EventEmitter. Menyediakan kontrol. Contoh pola ini disediakan oleh modul node-glob (https://npmjs.org/package/glob), sebuah library yang melakukan pencarian file bergaya glob. Titik masuk utama untuk modul adalah fungsi untuk mengekspor, yang memiliki tanda tangan berikut:
glob(pattern, [options], callback)
Fungsi ini mengambil fungsi callback yang dipanggil dengan pola, serangkaian opsi, dan daftar semua file yang cocok dengan pola yang disediakan sebagai argumen pertama. Pada saat yang sama, fungsi ini mengembalikan EventEmitter yang memberikan laporan lebih rinci tentang status proses. Misalnya, mendengarkan Event pertandingan akan memberi tahu Anda secara real time ketika pertandingan terjadi, mendapatkan daftar semua file yang cocok di Event akhir, atau mendengarkan untuk mengetahui apakah proses dibatalkan secara manual. Event pembatalan. Kode berikut menunjukkan seperti apa ini.
var glob = require('glob');
glob('data/*.txt', function(error, files) {
console.log('All files found: ' + JSON.stringify(files));
}).on('match', function(match) {
console.log('Match found: ' + match);
});
Seperti yang Anda lihat, Node.js biasanya memperlihatkan titik masuk yang sederhana, bersih, minimal sambil menyediakan fitur yang lebih canggih dan kurang penting sebagai sarana sekunder, EventEmitter. Adalah salah satu cara untuk menggabungkan dengan callback tradisional. Mencapainya.
PetunjukPola: Buat fungsi yang menerima callback dan mengembalikan EventEmitter. Ini menggunakan EventEmitter untuk memublikasikan Event yang lebih halus sambil memberikan titik masuk yang sederhana dan jelas untuk fungsionalitas utama.