Advertisement
  1. Code
  2. Web Development

استفاده از ماژول Event در برنامه نویسی Node

Scroll to top
Read Time: 13 min

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 برای چیزهایی که ما در بالا در مورد آنها صحبت کردیم بیابید.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.