node.js ile geliştirme yaparken (her yeni başlayan gibi) benim de en zorlandığım konu asenkron kod yazmaya alışmak oldu. Arkaplanda işlemler olaylarla (events) yürüdüğü için, eğer senkron işlem yapacaksanız ya callback kullanmak ya da events modülünü kullanmak gerekiyor. callback yöntemini kullanırsanız, yazdığınız koddan sonra ekrana bir süre anlamsız bir şekilde bakabilirsiniz. Hatta bu anlamsız bakış kodu her okumaya çalışmanızda da oluşur.
events modülü node.js ile birlikte gelmekte. Bu modül içindeki EventEmitter sınıfını extend ederek sınıflarınızı yazabilirsiniz ve böylelikle yazdığınız modüllerin daha işlevsel olmasını sağlarsınız.
Bir uygulama ile konuyu örneklendirelim. Bu masum uygulamamızda kullanıcı adı ve parolayı kontrol ettirip, durum olumlu ise kullanıcıya ait bir model oluşturacağız. İlk örnek kodlar callback yöntemi için.
ApiClientCallback.js:
var ApiClientCallback = function(){};
ApiClientCallback.prototype = {
getUserModel : function(object, callback){
var model = new Object();
model.username = object.username;
model.password = object.password;
callback(false, model);
},
isUser : function(object, callback){
if (object.username == "admin" && object.password == "admin")
{
this.getUserModel(object, callback);
}else{
callback("User not found", null)
}
}
};
exports.ApiClientCallback = ApiClientCallback;
callbackTest.js
var ApiClientCallback = require("./ApiClientCallback.js").ApiClientCallback;
var client = new ApiClientCallback();
client.isUser({
username : "admin",
password : "admin"
}, function(err, model){
if (err)
{
console.log(err);
}else{
console.log("username : " + model.username + " password : " + model.password);
}
});
Açıkçası bu yöntemin yönetimi bana zor geliyor. Önyargı mı bilemiyorum ancak 6. his debug işlemleri, kod güncellemeleri vb. konularda bu yöntemin epey soruna neden olacağını söylüyor. Çünkü koda her baktığımda kimin kimi çağırdığını kontrol etmem gerekiyor. Bunları takip etmek bu tür ufak kodlar için zor değil belki ama kod satır sayısı arttıkça iç içe dallanma ile bu işlem işkenceye dönüşebilir.
Şimdi aynı örneği events modülü ile yapalım:
ApiClientEvent.js
// events modülünü yükledik.
var events = require("events");
// Balatıcımızı oluşturduk.
var ApiClientEvents = function(){
events.EventEmitter.call(this);
// Olaylara sınıf metodlarımızı bağlıyoruz.
this.on(this.ON_IS_USER_SUCCESS, this.onIsUserSuccess);
this.on(this.ON_IS_USER_FAIL, this.onIsUserError);
this.on(this.ON_USER_MODEL_READY, this.onUserModelReady);
};
// Sınıfımızın EventEmitter sınıfını extend etmesini sağladık.
ApiClientEvents.super_ = events.EventEmitter;
ApiClientEvents.prototype = Object.create(events.EventEmitter.prototype, {
constructor : {
value : ApiClientEvents,
enumerable : false
}
});
// Artık sınıfımızla ilgilenmeye başlayabiliriz.
// Öncelikle olay isimlerimizi tanımlıyoruz. Sınıfla birlikte kullanırken kolaylık oluyor.
ApiClientEvents.prototype.ON_PREFIX = "ApiClientEventsOn";
ApiClientEvents.prototype.ON_IS_USER_SUCCESS = ApiClientEvents.prototype.ON_PREFIX + "IsUserSuccess";
ApiClientEvents.prototype.ON_IS_USER_FAIL = ApiClientEvents.prototype.ON_PREFIX + "IsUserFail";
ApiClientEvents.prototype.ON_USER_MODEL_READY = ApiClientEvents.prototype.ON_PREFIX + "UserModelReady";
// Kullanıcıyı bu metodla kontrol edeceğiz.
ApiClientEvents.prototype.isUser = function(object){
var self = this;
if (object.username == "admin" && object.password == "admin")
{
// İşlem başarılıysa ON_IS_USER_SUCCESS olayını tetikle. Parametre olarak object i ver.
self.emit(self.ON_IS_USER_SUCCESS, object);
}else{
// İşlem başarısız ise ON_IS_USER_FAIL olayını tetikle. Parametre olarak object i ver.
self.emit(self.ON_IS_USER_FAIL, object);
}
return self;
};
// Kullanıcı için bu metodla model oluşturacağız.
ApiClientEvents.prototype.createUserModel = function(object){
var self = this;
var model = new Object();
model.isLogged = true;
model.username = object.username;
model.password = object.password;
// Model oluşturma işleminden sonra modelin hazır olduğunu ON_USER_MODEL_READY ile duyur.
self.emit(self.ON_USER_MODEL_READY, model);
return self;
};
// Olay metodlarımız
ApiClientEvents.prototype.onIsUserSuccess = function(object){
console.log("onIsUserSuccess");
var self = this;
self.createUserModel(object);
return self;
};
ApiClientEvents.prototype.onIsUserError = function(object){
console.log("onIsUserError");
};
ApiClientEvents.prototype.onUserModelReady = function(model){
console.log("onUserModelReady");
console.log(model);
};
exports.ApiClientEvents = ApiClientEvents;
events_test.js:
var ApiClientEvents = require("./ApiClientEvents.js").ApiClientEvents;
var client = new ApiClientEvents;
client.on(client.ON_USER_MODEL_READY, function(model){
console.log("Buradayım 1");
});
client.on(client.ON_USER_MODEL_READY, function(model){
console.log("Buradayım 2");
});
var check = client.isUser({
username : "admin",
password : "admin"
});
events modülü ile daha çok kod yazıldığının farkındayım. Ancak daha temiz kodlara sahip olduğumuz kesin. Peki ne yaptık biz bu kodlarda?
İlk başta EventEmitter sınıfını extend ettik. Bu bizim sınıfımıza olayları yönetme özelliği kazandırdı. EventEmitter sınıfının genelde ihtiyaç duyulan on ve emit adlı iki metodu var. on metodu ile ilgili olay için fonksiyonları tanımladık. Aynı olaya birden çok fonksiyon tanımlanabilir. Bu tanımlanan fonksiyonları emit metodu ile çağırdık. emit ile çağrılan bir olay, on ile tanımlı tüm fonksiyonları sırasıyla tetikler.
Olayları sınıfınız içinde tanımlayabildiğiniz gibi, sınıfı kullanacak olanların tanımlaması için olayları hiç tanımlamadan da geliştirmenizi yapabilirsiniz.
Sınıf içinde isimleri ayrı bir değişkene aldım. Bu kullanım açısından epey kolaylık sağlıyor. Aynı zamanda bu yöntemle daha tekil isimler kullanabilirsiniz. Olaylar için bir ön ek belirlemeniz de bu tekilliği sağlamanıza yardımcı olur.