استفاده از ماژول Event در برنامه نویسی Node
Arabic (العربية/عربي) translation by Asadollah Irani (you can also view the original English article)
اولین باری که در مورد Node.js شنیدم، با خودم فکر کردم که فقط یک پیاده سازی از جاوااسکریپت برای سرور هستش. ولی در اصل چیزی بیشتر از اینه، Node.js تعدادی تابع توکار داره که توی جاوااسکریپت سمت کلاینت نمی تونیم اونها را پیدا کنیم. یکی از همین توایع توکار، ماژول Event هستش که کلاسی به نام EventEmitter
داره. ما می خوایم که در این آموزش نگاهی بر این ماژول بیاندازیم.
EventEmitter
: چی هست و برای چی استفاده میشه
یکی از مزیتهای رویدادها اینه که: رویدادها یک مسیر بسیار آزاد هستند که قسمت های مختلف کد را به یکدیگر متصل می کنه.
حالا در اصل کلاس EventEmitter
چه کاری انجام میده؟ به زبان ساده، این کلاس به شما اجازه میده که به رویداد ها گوش کنید و یک عملیات را به اون رویداد اختصاص بدید که هر وقت اون رویداد رخ داد عملیات ما انجام بشه. اگر شما با جاوااسکریپت سمت کلاینت آشنا باشید، می دونید که رویدادهایی مثل رویداد ماوس و کیبرد در نتیجه فعالیت های کاربران اتفاق می افتند. این دو خیلی شبیه به همند به جز این که توی Node.js ما هر زمانی که بخوایم می تونیم یک رویداد را صادر کنیم بدون این که نیازی باشه که مثلاً کاربر یه دکمه کیبرد را فشار بده و یا ماوس را تکون بده. اصل و اصول EventEmitter
بر اساس مدل انتشار/اشتراک (pub/sub) بنا شده، به این دلیل که ما می تونیم مشترک یک رویداد بشیم و یا یه رویداد را منتشر کنیم. تعداد زیادی کتابخانه برای جاوااسکریپت سمت کلاینت ساخته شده که این مدل رو پشتیبانی می کنه ولی Node اون را به صورت توکار و از قبل ساخته شده در داخل خودش داره.
یکی دیگر از سوال های مهم اینه که چرا ما اصلاً باید از مدل رویداد استفاده کنیم؟ توی Node.js ما از رویداد ها می تونیم به جای استفاده callback های تودرتو استفاده کنیم. خیلی از متدهای Node به صورت غیر همزمان اجرا میشند، که این به این معنی که برای اجرای کد بعد از این که متد به پایان رسید، باید یک callback به تابع ارسال کنید. در آخر، کد شما مثل یک قیف بزرگ به نظر می رسه. برای جلوگیری از این اتفاق، خیلی از کلاس های Node، رویدادهایی را از خودشون خارج می کنند که شما می تونید به آنها گوش دهید. این روش به شما اجازه میده تا کدهاتون را به روشی که می خوید سروسامان بدید و مجبور به استفاده از روش ارسال callback نباشید.
آخرین مزیت رویدادها اینه که رویدادها یک راه آزاد برای وصل کردن قسمت های مختلف کدتون به یکدیگرند. اگر کدی برای گوش دادن به رویدادی که صادر شده وجود نداشته باشه، مشکلی پیش نمیاد. این یعنی این که که اگه ما قسمتی از کد رو که به رویدادی گوش میده رو حذف کنیم خطایی رخ نمیده.
استفاده از EventEmitter
ما با کلاس EventEmitter
کارمون رو شروع می کنیم. این کار خیلی ساده است، فقط باید ماژول events را با استفاده از دستور require به کدمون اضافه کنیم.
1 |
|
2 |
var events = require("events"); |
شی events
فقط یک عنصر به نام کلاس EventEmitter
داره. بیاید برای شروع یه مثال ساده ایجاد کنیم:
1 |
|
2 |
var EventEmitter = require("events").EventEmitter; |
3 |
|
4 |
var ee = new EventEmitter(); |
5 |
ee.on("someEvent", function () { |
6 |
console.log("event has occured"); |
7 |
});
|
8 |
|
9 |
ee.emit("someEvent"); |
ما کارمون رو با ایجاد یک شی جدید از کلاس EventEmitter
شروع می کنیم. این شی دو متد اصلی داره که ما برای رویداد ها از اینا استفاده می کنیم: یکی on
و یکی دیگه هم emit
ابتدا به سراغ متد on
می ریم. این متد دو پارامتر دریافت می کند: اولین پارامتر نام رویدادی است که قراره که ما به اون گوش کنیم که در اینجا اسمش "someEvent"
می باشد. که البته شما میتونید اسم بهتری انتخاب کنید. و دومین پارامتر تابعی هستش که قراره وقتی رویداد رخ داد صدا زده شود. این تموم اون چیزی هست که ما برای راه اندازی یه رویداد به اون نیاز داریم.
توی خط آخر از کد بالا ما رویداد را صادر کرده ایم. برای این کار فقط باید نام رویداد را به متد emit
از شی EventEmitter
ارسال کنید. . اگر شما کد بالا را اجرا کنید می بینید که متن در کنسول چاپ شده است.
کد بالا مهم ترین پایه و اساس استفاده از EventEmitter
هستش. همچنین شما می تونید هنگامی که رویدادی رخ میده اطلاعاتی را نیز اضافه کنید.
1 |
|
2 |
ee.emit("new-user", userObj); |
توی کد بالا فقط یه پارامتر دیتا وجود داره، در حالی که شما می تونید تعداد بیشتری از اطلاعات را ارسال کنید. برای اینکه بتونید از این اطلاعات توی تابع کنترل کننده رویداد استفاده کنید باید اونها را به صورت پارامتر دریافت کنید.
1 |
|
2 |
ee.on("new-user", function (data) { |
3 |
// use data here
|
4 |
});
|
قبل از ادامه اجازه بدید که قسمتی از عملکرد EventEmitter
را برای شما روشن کنم. ما می تونیم بیشتر از یک شنونده برای هر رویداد داشته باشیم؛ شنونده های چندگانه را می تونید با استفاده از متد on
به یک رویداد اخصاص بدیم و تمامی توابع هنگامی که رویداد اجرا شود فراخوانی می شوند. به طور پیش فرض Node به شما اجازه میده که برای هر رویداد 10 شنونده قرار بدید و در صورتی که این تعداد از 10 بیشتر بشه Node هشداری صادر می کنه. به هر حال ما می تونیم این تعداد را با استفاده از setMaxListeners
تغییر بدیم. برای مثال، اگر این کد رو اجرا کنید هشداری برای شما چاپ می شه.
1 |
|
2 |
ee.on("someEvent", function () { console.log("event 1"); }); |
3 |
ee.on("someEvent", function () { console.log("event 2"); }); |
4 |
ee.on("someEvent", function () { console.log("event 3"); }); |
5 |
ee.on("someEvent", function () { console.log("event 4"); }); |
6 |
ee.on("someEvent", function () { console.log("event 5"); }); |
7 |
ee.on("someEvent", function () { console.log("event 6"); }); |
8 |
ee.on("someEvent", function () { console.log("event 7"); }); |
9 |
ee.on("someEvent", function () { console.log("event 8"); }); |
10 |
ee.on("someEvent", function () { console.log("event 9"); }); |
11 |
ee.on("someEvent", function () { console.log("event 10"); }); |
12 |
ee.on("someEvent", function () { console.log("event 11"); }); |
13 |
|
14 |
ee.emit("someEvent"); |
برای اینکه حداکثر تعداد مشاهده کننده ها رو افزایش بدید، این خط را بالای گوش کننده ها اضافه کنید:
1 |
|
2 |
ee.setMaxListeners(20); |
و حالا اگر کد رو اجرا کنید دیگه هشداری را نمی بینید.
متدهای دیگه ی EventEmitter
تعدادی متد دیگر از EventEmitter
وجود داره که می تونه مفید باشه.
یکی از اونا once
هستش. این متد دقیقاً مشابه on
می باشد، با این تفاوت که تنها یک بار کار اجرا میشه. بعد از این که برای اولین بار فراخوانی شد، شنونده حذف خواهد شد.
1 |
|
2 |
ee.once("firstConnection", function () { console.log("You'll never see this again"); }); |
3 |
ee.emit("firstConnection"); |
4 |
ee.emit("firstConnection"); |
اگر شما کد بالا را اجرا کنید می بینید که پیغام فقط یک بار چاپ می شود. صادر کننده رویداد دوم توسط هیچ شنونده ای دریافت نمیشه به این دلیل که شنونده once
بعد از استفاده حذف می شود.
خود ما هم می تونیم به چند روش به صورت دستی تابع گوش دهنده را حذف کنیم. اولین روش اینه که یک گوش دهنده را با استفاده از متد removeListener
حذف کنیم. این متد دو تا پارامتر می گیره: اولی نام رویداد و دومی تابع گوش دهنده. تا الان ما از توابع بی نام به عنوان گوش دهنده استفاده می کردیم. اگر بخوایم که بعداً قادر به حذف یک گوش دهنده باشیم باید که گوش دهنده ی ما اسم داشته باشه یعنی از توابع بدون نام نمی تونیم استفاده کنیم تا بتونیم به آن ارجاع دهیم. ما می تونیم از متد removeListener
استفاده کنیم تا قابلیت های متد once
را شبیه سازی کنیم.
1 |
|
2 |
function onlyOnce () { |
3 |
console.log("You'll never see this again"); |
4 |
ee.removeListener("firstConnection", onlyOnce); |
5 |
}
|
6 |
|
7 |
ee.on("firstConnection", onlyOnce) |
8 |
ee.emit("firstConnection"); |
9 |
ee.emit("firstConnection"); |
اگر کد بالا را اجرا کنید می بینید که عملکرد مشابه متد once
داره.
اگر شما بخوید که تمام گوش دهنده هایی که به یک رویداد وصل هستند رو حذف کنید می تونید از متد removeAllListeners
استفاده کنید و تنها کافیست نام رویداد مورد نظر را به آن ارسال کنید.
1 |
|
2 |
ee.removeAllListeners("firstConnection"); |
برای اینکه تمام گوش دهنده های تمام رویداد ها را حذف کنید کافیست که متد قبلی را بدون هیچ پارامتری فراخوانی کنید.
1 |
ee.removeAllListeners(); |
و آخرین متد: listener
این متد نام یه رویداد رو به عنوان پارامتر دریافت و آرایه ای از توابعی که به این رویداد گوش می دهند را به شما برمیگردونه. یه مثال از این متد، بر اساس مثال onlyOnce
را میتونید در زیر ببینید:
1 |
|
2 |
function onlyOnce () { |
3 |
console.log(ee.listeners("firstConnection")); |
4 |
ee.removeListener("firstConnection", onlyOnce); |
5 |
console.log(ee.listeners("firstConnection")); |
6 |
}
|
7 |
|
8 |
ee.on("firstConnection", onlyOnce) |
9 |
ee.emit("firstConnection"); |
10 |
ee.emit("firstConnection"); |
ما این بخش را با کمی از متا بودن به پایان می بریم.(نکته مترجم: نفهمیدم متابودن یعنی چی! فک کنم منظورش یه سری مشخصات درونی این شی باشه.) نمونه ی EventEmitter
ما، دو رویداد اجرا می که که ما می تونیم به اونها گوش کنیم: یکی از این رویداد ها زمانی صادر می شود که یک گوش کننده ی جدید ایجاد می شود و اون یکی زمانی که یک گوش کننده حذف می شود. اینجا را ببینید:
1 |
|
2 |
ee.on("newListener", function (evtName, fn) { |
3 |
console.log("New Listener: " + evtName); |
4 |
});
|
5 |
|
6 |
ee.on("removeListener", function (evtName) { |
7 |
console.log("Removed Listener: " + evtName); |
8 |
});
|
9 |
|
10 |
function foo () {} |
11 |
|
12 |
ee.on("save-user", foo); |
13 |
ee.removeListener("save-user", foo); |
هنگام اجرای این کد شما می بینید که هر بار که یک گوش دهنده اضافه و حذف می شود پیغامی که انتظارش رو داشتیم را دریافت می کنیم.
حالا که تمام متد هایی که یک نمونه از EventEmitter
دارد را دیدیم، بیایید تا با نحوه ی استفاده از آنها در داخل ماژول ها هم آشنا بشیم.
استفاده از EventEmitter
در داخل ماژول ها
از اونجایی که EventEmitter
فقط جاوااسکریپت عادی هستش این حس را ایجاد می کنه که می تونیم از اون در داخل ماژول هامون استفاده کنیم. توی ماژول های جاوااسکریپت خودتون، می تونید یک نمونه از EventEmitter
را ایجاد کنید و از آن برای کنترل رویداد های داخلی استفاده کنید. که این کار خیلی ساده ایه. جالب تر اینه که ما می تونیم ماژول هایی را ایجاد کنیم که از EventEmitter
ویژگی هایی را به ارث ببرند، بنابراین ما از عملکردهای اون می تونیم به عنوان بخشی از Api های عمومی بهره ببریم.
در واقع ماژول های توکاری در Node وجود دارد که دقیقاً این کار را انجام می دهند. برای مثال شاید شما با ماژول http
آشنا باشید، این یه ماژول هستش که از اون برای ساخت وب سرور استفاده می کنید. این مثال به شما نشان می ده که چگونه متد on
از کلاس EventEmitter
بخشی از کلاس http.Server
شده است:
1 |
|
2 |
var http = require("http"); |
3 |
var server = http.createServer(); |
4 |
|
5 |
server.on("request", function (req, res) { |
6 |
res.end("this is the response"); |
7 |
});
|
8 |
|
9 |
server.listen(3000); |
اگر شما این کد را اجرا کنید، فرآیند منتظر یک درخواست می شود، شما می تونید به آدرس http://localhost:3000
برید و یک پاسخ دریافت خواهید کرد. وقتی که سرور یه درخواست از سمت مرورگر دریافت می کنه یک رویداد "request"
صادر می کنه و گوش دهنده ی ما یک رویداد دریافت می کنه و می تونه بر اساس اون عکس العمل نشان بده.
پس، چه شکلی می تونیم یک کلاس ایجاد کنیم که خواصی رو از EventEmitter
به ارث ببره؟ در اصل کار زیاد سختی نیست. ما یک کلاس به نام UserList
ایجاد میکنیم که قراره اشیا user را کنترل کنه. پس توی فایل userlist.js کارمون رو با این کد شروع می کنیم:
1 |
|
2 |
var util = require("util"); |
3 |
var EventEmitter = require("events").EventEmitter; |
ما به ماژول util
برای ارث بری احتیاج داریم. سپس ما به دیتابیس احتیاج داریم: به جای استفاده از یه دیتابیس واقعی، ما از یک شی استفاده می کنیم.
1 |
|
2 |
var id = 1; |
3 |
var database = { |
4 |
users: [ |
5 |
{ id: id++, name: "Joe Smith", occupation: "developer" }, |
6 |
{ id: id++, name: "Jane Doe", occupation: "data analyst" }, |
7 |
{ id: id++, name: "John Henry", occupation: "designer" } |
8 |
]
|
9 |
};
|
حالا ما می تونیم ماژول خودمون را ایجاد کنیم. اگه شما با ماژول های Node آشنا نیستید، روش کار اونها به این صورته که:هر کد جاوااسکریپتی که در داخل این فایل می نویسیم به طور پیش فرض تنها در داخل همین فایل قابل خوندنه. اگر ما بخوایم کد را به صورت یه Api عمومی دربیاوریم باید اون را جزئی از خاصیت module.exports
قرار بدیم و یا کل شی جدید و یا تابع را برابر module.exports
قرار بدیم. بیایید تا این کار را انجام بدیم:
1 |
|
2 |
function UserList () { |
3 |
EventEmitter.call(this); |
4 |
}
|
تابع بالا یه تابع سازندست، ولی نه یک تابع سازنده معمولی جاوااسکریپت. کاری که ما اینجا انجام می دیم استفاده از متد call
داخل سازنده EventEmitter
می باشد برای اینکه زمانی که شی جدیدی از UserList
ساخته شد این متد اجرا بشه. اگر قرار باشه هر کاری رو در هنگام مقدار دهی اولیه شی انجام بدیم باید اون کار رو در داخل این تابع قرار بدیم، ولی فعلاً این همه اون چیزیه که فعلاً می خواهیم انجام بدیم.
ارث بری سازنده به خودی خود کافی نیست و ما به ارث بری prototype هم احتیاج داریم. اینجا جاییه که ماژول util
وارد عمل می شود.
1 |
|
2 |
util.inherits(UserList, EventEmitter); |
این ماژول همه ی ویژگی های EventEmitter.prototype
را به UserList.prototype
اضافه می کند. و حالا نمونه های کلاس UserList
تمام متدهای یک نمونه ی کلاس از EventEmitter
را دارا می باشند. اما ما چند متد دیگه هم می خوایم اضافه کنیم. ما می خوایم متد save
را اضافه کنیم که بمون اجازه می ده تا کاربران جدیدی را اضافه کند.
1 |
|
2 |
UserList.prototype.save = function (obj) { |
3 |
obj.id = id++; |
4 |
database.users.push(obj); |
5 |
this.emit("saved-user", obj); |
6 |
};
|
این تابع یک شی دریافت و اون توی دیتابیس ما ذخیره می کنه: این تابع یک id
به شی ما اضافه و اون رو به آرایه ی کاربران اضافه می کند. سپس، رویداد "saved-user"
را صادر می کند و شی را به عنوان دیتا به تابع گوش دهنده ی آن ارسال می کند. اگه دیتابیس ما واقعی باشد عملیات ذخیره کردن را به صورت غیر همزمان انجام می شود به این معنا که برای کار کردن با رکوردهای ذخیره شده ما باید یک تابع callback نیز بپذیریم. روش دیگر برای این کار اینه که یک رویداد صادر کنیم کاری که ما می خوایم انجام بدیم. حالا اگر ما بخوایم با رکورد های ذخیره شده کاری انجام بدیم کافیه که به رویداد اون گوش کنیم. این کار رو ما تا لحظه ای دیگر انجام می دیم. بزارید از نزدیک نگاهی به UserList
بندازیم
1 |
|
2 |
UserList.prototype.all = function () { |
3 |
return database.users; |
4 |
};
|
5 |
|
6 |
module.exports = UserList; |
من یک متد دیگه اضافه کردم: متدی که تموم کاربران رو به ما بازمی گردونه. سپس ما UserList
را برابر module.exports
قرار می دهیم.
حالا بیایید این کار را به صورت عملی ببینیم؛ توی فایلی دیگه به نام test.js
کد زیر را اضافه می کنیم:
1 |
|
2 |
var UserList = require("./userlist"); |
3 |
var users = new UserList(); |
4 |
|
5 |
users.on("saved-user", function (user) { |
6 |
console.log("saved: " + user.name + " (" + user.id + ")"); |
7 |
});
|
8 |
|
9 |
users.save({ name: "Jane Doe", occupation: "manager" }); |
10 |
users.save({ name: "John Jacob", occupation: "developer" }); |
بعد از اضافه کردن ماژول به برنامه و ساختن یک نمونه از اون، ما به رویداد "saved-user"
گوش می دیم سپس ما می تونیم تعدادی کاربر را ذخیره کنیم. وقتی که ما این برنامه را اجرا کنیم، دو پیغام را مشاهده می کنیم، نام و id رکوردهایی که ما ذخیره کرده ایم.
1 |
|
2 |
saved: Jane Doe (4) |
3 |
saved: John Jacob (5) |
البته، ما می تونیم به روش دیگری نیز این کار را انجام بدیم، ما تونیم از متد on در داخل کلاس خودمان و متد emit در بیرون از کلاس استفاده کنیم و یا اینکه هر دوی آنها در داخل و یا خارج از کلاس باشند. اما همین یه مثال خوبه که نشون میده چگونه می توان این کار را انجام داد.
نتیجه گیری
کلاس EventEmitter
چگونه کار می کند. در زیر این مطلب شما می توانید لینک هایی را به اسناد Node برای چیزهایی که ما در بالا در مورد آنها صحبت کردیم بیابید.
- ماژول رویدادهای Node
- ماژول Util نود
- Node HTTP Agent Source - این مطب به شما الگوی ارث بری که ما در اینجا از آن استفاده کردیم را نشان می دهد.