آموزش استفاده از Event Emitterها در Node.js

مقدمه رویدادها (Events) در Node.js توسط آبجکتهایی به نام Event Emitter (ساطعکننده رویداد) ایجاد میشوند که با ارسال یک پیام، وقوع یک عملیات را علامتگذاری میکنند. توسعهدهندگان جاوااسکریپت میتوانند کدی بنویسند که به رویدادهای یک Event Emitter گوش میدهد (Listen)، که این به آنها اجازه میدهد هر بار این رویدادها رخ دادند، توابع خاصی را اجرا کنند. در این زمینه، رویدادها از یک رشته شناساییکننده (Identifying String) و هر دادهای که باید به شنوندگان (Listeners) ارسال شود، تشکیل میشوند.
معمولاً در Node.js، زمانی که میخواهیم عملی پس از تکمیل عملی دیگر انجام شود، از تکنیکهای برنامهنویسی ناهمگام (Asynchronous) مانند توابع Callback تو در تو یا زنجیرهای کردن Promiseها استفاده میکنیم. با این حال، این تکنیکها عملیات راهانداز و عملیات نهایی را به شدت به هم متصل (Couple) میکنند، که اصلاح عملیات نهایی را در آینده دشوار میسازد. Event Emitterها راه متفاوتی برای ساختاربندی این رابطه ارائه میدهند: الگوی انتشار-اشتراک (Publish-Subscribe). در این الگوی معماری نرمافزار، یک ناشر (Publisher – همان Event Emitter) پیامی (رویدادی) را ارسال میکند و یک مشترک (Subscriber) رویداد را دریافت کرده و عملی را انجام میدهد. قدرت این الگو در این است که ناشد نیازی ندارد از مشترکها اطلاع داشته باشد. ناشر پیام را منتشر میکند و واکنش به آن به عهده مشترکهاست. اگر بخواهیم رفتار اپلیکیشن خود را تغییر دهیم، میتوانیم نحوه واکنش مشترکها را بدون نیاز به تغییر ناشد تنظیم کنیم.
در این مقاله، ما یک Event Listener (شنونده رویداد) برای یک کلاس جاوااسکریپتی به نام TicketManager ایجاد میکنیم که به کاربر اجازه خرید بلیط میدهد. ما شنوندههایی برای رویداد buy تنظیم خواهیم کرد که هر بار یک بلیط خریداری میشود، فعال میشوند. این فرآیند همچنین نحوه مدیریت رویدادهای خطا از سمت Emitter و نحوه مدیریت مشترکهای رویداد را نیز نشان میدهد.
پیشنیازها
- Node.js روی ماشین توسعه شما نصب باشد. این آموزش با Node.js نسخه 20 تست شده است. برای نصب آن روی اوبونتو 24.04، میتوانید به مستندات رسمی پارمین کلود (Parmin Cloud) مراجعه کنید.
- آشنایی با کلاسهای جاوااسکریپت (ES6).
مرحله ۱ — ساطع کردن (Emitting) رویدادها
در این مرحله، دو روش رایج برای ایجاد یک Event Emitter در Node.js را بررسی میکنیم. اولی استفاده مستقیم از آبجکت Event Emitter است و دومی ایجاد آبجکتی است که از Event Emitter ارثبری (Extend) میکند.
تصمیمگیری برای انتخاب یکی از این دو روش بستگی به میزان اتصال (Coupling) رویدادهای شما به عملیات آبجکتهایتان دارد. اگر رویدادهایی که میخواهید ساطع کنید نتیجه اعمال یک آبجکت هستند، احتمالاً بهتر است از کلاس Event Emitter ارثبری کنید تا برای راحتی کار به توابع آن دسترسی داشته باشید. اگر رویدادها مستقل از آبجکتهای کسبوکار شما هستند، یک آبجکت مستقل Event Emitter ایجاد میکنید که توسط آبجکتهای شما ارجاع داده میشود.
ابتدا یک پوشه برای ذخیره کدهای خود ایجاد کنید. در ترمینال خود، یک پوشه جدید به نام event-emitters بسازید:
اولین فایل را با نام firstEventEmitter.js در ویرایشگر متن باز کنید:
در Node.js، ما رویدادها را از طریق کلاس EventEmitter ساطع میکنیم. این کلاس بخشی از ماژول events است. ابتدا این ماژول را وارد کنید:
حالا با اضافه کردن خط زیر، یک رویداد را ساطع کنید:
تابع emit() برای شلیک رویدادها استفاده میشود. ما باید نام رویداد را به عنوان یک رشته به آن پاس دهیم. میتوانیم هر تعداد آرگومان را بعد از نام رویداد اضافه کنیم. رویدادهایی که فقط نام دارند محدود هستند؛ آرگومانهای دیگر به ما اجازه میدهند دادههایی را به شنوندگان بفرستیم.
فایل را ذخیره کرده و با دستور node آن را اجرا کنید:
زمانی که اسکریپت اجرای خود را تمام میکند، هیچ خروجی در ترمینال نخواهید دید. زیرا ما هیچ پیامی را لاگ نکردیم و چیزی وجود ندارد که به رویداد ارسال شده گوش دهد. رویداد ساطع شده است، اما هیچ چیز به آن واکنش نشان نمیدهد.
حالا بیایید یک مثال کاملتر از انتشار، شنود و واکنش به رویدادها بسازیم. ما این کار را با ایجاد یک اپلیکیشن نمونه مدیریت بلیط انجام میدهیم. مدیریت بلیط تابعی برای خرید بلیط ارائه میدهد. وقتی بلیطی خریداری میشود، رویدادی همراه با جزئیات خریدار ارسال میشود.
فایل جدیدی به نام ticketManager.js ایجاد کنید:
مانند مثال قبل، باید کلاس EventEmitter را وارد کنیم:
در اینجا، کلاس TicketManager از کلاس EventEmitter ارثبری میکند. این یعنی کلاس TicketManager متدها و ویژگیهای EventEmitter را به ارث میبرد و به متد emit() دسترسی پیدا میکند.
ما میخواهیم تعداد بلیطهای اولیه را در سازنده (Constructor) تعریف کنیم:
سازنده یک آرگومان supply (موجودی) دارد. حتی اگر اعلام کردیم TicketManager یک کلاس فرزند EventEmitter است، هنوز باید super() را فراخوانی کنیم تا به متدها و ویژگیهای EventEmitter دسترسی داشته باشیم.
حالا متد buy() را اضافه میکنیم:
در متد buy()، ایمیل خریدار و قیمت بلیط را دریافت میکنیم، موجودی را یکی کم کرده و یک رویداد buy را همراه با دادههای اضافی (ایمیل، قیمت و تایماستمپ) ساطع میکنیم. در نهایت کلاس را اکسپورت (Export) میکنیم. فایل را ذخیره کرده و ببندید.
مرحله ۲ — گوش دادن به رویدادها
Node.js به ما اجازه میدهد با تابع on() یک شنونده برای یک رویداد اضافه کنیم. این متد به نام رویداد خاصی گوش میدهد و پس از رخ دادن رویداد، یک تابع Callback را اجرا میکند.
فایل جدیدی به نام firstListener.js ایجاد کنید:
ابتدا TicketManager را وارد کنید:
حالا اولین شنونده رویداد خود را برای رویداد buy اضافه میکنیم:
برای اضافه کردن شنونده جدید، از تابع on() استفاده کردیم. آرگومان دوم آن یک تابع Arrow (تابع Callback) است که پس از ساطع شدن رویداد اجرا میشود. سپس با فراخوانی buy() رویداد را شلیک میکنیم.
فایل را ذخیره و اجرا کنید:
خروجی در کنسول شما این خواهد بود:
اگر متد buy() را دوبار صدا بزنید، پیام دو بار چاپ میشود، زیرا شنونده هر دو رویداد را دریافت میکند.
گاهی اوقات ما فقط به اولین باری که یک رویداد رخ میدهد علاقهمندیم. Node.js برای این مورد تابع once() را ارائه کرده است. وقتی رویدادی توسط شنوندهای که با once() ثبت شده دریافت میشود، Node.js به طور خودکار آن شنونده را حذف کرده و سپس کد را اجرا میکند.
مرحله ۳ — دریافت دادههای رویداد
تا اینجا شنوندههایی تنظیم کردید که به رویدادهای ساطع شده واکنش نشان میدهند. رویدادهای ساطع شده همچنین دادههایی را همراه خود دارند. بیایید ببینیم چگونه میتوانیم این دادهها را دریافت کنیم.
ما چند ماژول جدید Node.js ایجاد میکنیم: یک سرویس ایمیل و یک سرویس دیتابیس.
فایل emailService.js را ایجاد کنید:
سپس فایل databaseService.js را ایجاد کنید:
حالا فایل index.js را ایجاد کنید تا این ماژولها را به هم متصل کند:
ماژولها را وارد کرده و آبجکتها را نمونهسازی (Instantiate) کنید:
در این کد، تابع Callback ما سه آرگومان دارد که با دادههای ساطع شده از طرف emit() مطابقت دارد.
فایل را ذخیره و اجرا کنید:
خروجی زیر را مشاهده خواهید کرد:
دادهها با موفقیت دریافت شده و در توابع ما استفاده شدند.
مرحله ۴ — مدیریت رویدادهای خطا
اگر یک Event Emitter نتواند عملیات خود را انجام دهد، باید رویدادی را برای علامتگذاری شکست عملیات ساطع کند. در Node.js، روش استاندارد برای این کار ساطع کردن رویداد error است. این رویداد باید همراه با یک آبجکت Error باشد.
در حال حاضر، مدیریت بلیط ما با هر بار خرید، موجودی را یکی کاهش میدهد، اما چیزی مانع فروش بلیط بیشتر از موجودی نمیشود. بیایید متد buy() را طوری تغییر دهیم که اگر موجودی به 0 رسید، خطایی ساطع کند.
فایل ticketManager.js را باز کنید:
متد buy() را به شکل زیر ویرایش کنید:
فایل را ذخیره کنید. حالا فایل index.js را ویرایش کنید تا 4 بلیط بخریم (در حالی که موجودی 3 است):
به انتهای فایل خطوط زیر را اضافه کنید:
فایل را ذخیره و اجرا کنید:
برای سه خرید اول همه چیز خوب پیش میرود، اما در خرید چهارم برنامه کرش میکند:
اگر یک رویداد خطا ساطع شود و شنوندهای برای آن ثبت نکرده باشیم، Node.js خطا را پرتاب (Throw) کرده و برنامه را کرش میکند. بهترین روش این است که همیشه برای رویدادهای خطا شنونده تعریف کنید.
فایل index.js را دوباره باز کنید و شنونده خطا را اضافه کنید:
فایل را ذخیره و اجرا کنید. این بار به جای کرش کردن، خطا به شکل زیبایی مدیریت میشود:
مرحله ۵ — مدیریت شنوندگان رویداد
Event Emitterها مکانیزمهایی برای مانیتورینگ و کنترل تعداد شنوندگانی که در یک رویداد مشترک شدهاند دارند. برای دریافت تعداد شنوندگان یک رویداد، میتوانیم از متد listenerCount() استفاده کنیم.
فایل index.js را باز کنید و تمام فراخوانیهای buy() را پاک کنید. به جای آن کدهای زیر را اضافه کنید:
خروجی نشان میدهد که برای هر رویداد یک شنونده داریم.
در Node.js برای حذف یک شنونده از تابع off() استفاده میکنیم. این متد دو آرگومان میگیرد: نام رویداد و تابعی که به آن گوش میدهد. بنابراین برای حذف یک شنونده، تابع Callback آن باید در یک متغیر ذخیره شده باشد.
به انتهای فایل index.js اضافه کنید:
اگر فایل را اجرا کنید، میبینید که پس از حذف شنونده دوم، فقط شنونده اول (ارسال ایمیل و ذخیره در دیتابیس) به رویداد واکنش نشان میدهد.
اگر بخواهیم تمام شنوندگان یک رویداد خاص را حذف کنیم، میتوانیم از تابع removeAllListeners() استفاده کنیم:
پس از حذف تمام شنوندگان، دیگر هیچ واکنشی به خرید بلیطها صورت نمیگیرد.
نتیجهگیری
در این آموزش، یاد گرفتید که چگونه از Event Emitterهای Node.js برای راهاندازی رویدادها استفاده کنید. شما رویدادها را با emit() ساطع کردید، با on() و once() به آنها گوش دادید، رویدادهای خطا را مدیریت کردید و با listenerCount() و off() شنوندگان را مانیتور و مدیریت کردید.
با استفاده از Callbackها و Promiseها، سیستم مدیریت بلیط شما باید مستقیماً با ماژولهای ایمیل و دیتابیس ادغام میشد تا عملکرد یکسانی به دست آید. اما از آنجایی که از Event Emitterها استفاده کردیم، رویداد از پیادهسازیهای آن جدا (Decouple) شد. هر ماژولی که به مدیریت بلیط دسترسی داشته باشد، میتواند رویداد آن را مشاهده کرده و به آن واکنش نشان دهد. این معماری مقیاسپذیری (Scalability) بسیار خوبی به اپلیکیشن شما میبخشد.
از همراهی شما با پارمین کلود سپاسگزاریم.




