⚡ Event Dispatcher
The EventDispatcher
is the object responsible for notifying subscribers about an event.
They are used by the EventBus
, which passes to the dispatcher the subscribers and the arguments to use to notify them.
The dispatcher is then responsible that for each subscriber:
- The
SubscriberMiddlewareList
passes the execution. - The subscriber is executed with its respective arguments.
- Any errors thrown by the subscriber or middleware are caught, wrapped if needed, and redirected to the
ErrorHandler
.
The main reason dispatchers are separated from the bus is because of the different ways subscribers can be called. Nyx
takes into account two ways: SyncEventDispatcher
and AsyncEventDispatcher
.
While nyx/core
declares sync and async dispatchers, the EventBus
in reality only depends in the EventDispatcher
interface, so it's not necessary for a custom dispatcher to use the sync or async version.
🔂 Sync Dispatchers
The SyncEventDispatcher
is a special type of dispatcher which calls its subscribers synchronously, meaning that it
waits for the current subscriber to finish before executing the next. Its @framework
implementation is the
BasicSyncEventDispatcher
.
✅ Sync Advantages
-
Synchronous execution ensures that subscribers are only executed in the order of their
Priority
. -
Since synchronous execution waits for a subscriber to finish before moving on to the next one, it is easier to debug, as it is easier to trace where an error occurs.
❌ Sync Disadvantages
- Synchronous execution may be slower for events with many subscribers, as it has to wait for each subscriber (and its middleware) to finish before moving on to the next one. This can cause performance issues.
- If one subscriber takes a long time to execute, it can cause a delay in the entire system.
⏰ Sync Timeout
To tackle the mentioned disadvantages, the SyncEventDispatcher
has a sync timeout functionality. This allows you to
set "timeframe" for any subscriber to finish, before the dispatcher stops waiting and calls the next subscriber.
This functionality can be turned off by setting it to null
via #setSyncTimeout()
.
The default sync timeout is of 10000 ms
(10 seconds).
🔀 Async Dispatchers
The AsyncEventDispatcher
is a special type of dispatcher which calls its subscribers asynchronously, meaning that it
doesn't wait for the current subscriber to finish before executing the next. Its @framework
implementation is the
BasicAsyncEventDispatcher
.
✅ Async Advantages
- Asynchronous execution can be faster, especially for events with many subscribers, since it can execute them in parallel.
- Asynchronous execution is less prone to blocking or delaying the system, since it can move on to the next subscriber while the previous one is still executing.
❌ Async Disadvantages
- Multiple subscribers running in parallel may hog the event loop and decrease performance.
- May lead to calling subscribers in an order different than their priority, which can cause problems in scenarios where
the order of execution matters. For example, subscribers with
PriorityEnum.Normal
may execute before subscribers withPriorityEnum.High
have finished. - Since subscribers are executed in parallel, it can be harder to trace where an error occurs.
- Asynchronous execution may cause issues with subscribers that rely on the state of the system (such as the event handling marking system), as they may not see the most up-to-date state when they execute.
📋 Concurrency Limit
To tackle the mentioned disadvantages, the AsyncEventDispatcher
has a concurrency limit functionality. This allows you
to set how many subscribers are being checked or executed at the same time.
This functionality can be turned off by setting it to null
via #setConcurrencyLimit()
.
- Setting this limit to
1
effectively turns the dispatcher into aSyncEventDispatcher
. - The default concurrency limit is
3
.
🚦 Middleware
The dispatcher stores an SubscriberMiddlewareList
that will either allow or deny the call of a given subscriber.
This list can be obtained via EventDispatcher#getMiddleware()
.
For information about how to use it, check the ️🛡️ Event Interception documentation.
💫 Error Handling
The dispatcher stores an ErrorHandler
, which is redirected errors thrown by any subscriber. For information about how
to use it, check the 💫 Error Handling documentation.
There are special errors that are redirected there however, specifically when checking the middleware. These are:
EventSubscriberMiddlewareError
- Caused when a middleware throws an error, the list catches it and wraps it in this error. It's known which middleware caused the error.UncaughtEventSubscriberMiddlewareError
- Caused when the list throws an error, and it's not the one above. In theory, it should never happen but it still exists just in case. It's not known which middleware caused the error.
These errors hold information useful to handle them, like the middleware that caused the error, or the entire list if the middleware is not known.