Event
Last updated
Last updated
Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
Node.js emphasizes its event-driven, non-blocking I/O model, which makes it lightweight and efficient. This event-driven model is used extensively in many core modules of Node.js, making it one of the most important modules in the entire Node.js ecosystem.
The above diagram is a UML class diagram.
The observer pattern is a design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
When a subject needs to notify observers about something interesting happening, it broadcasts a notification to the observers (which can include specific data related to the topic of the notification).
The deeper motivation for using the observer pattern is to avoid tight coupling between objects when we need to maintain consistency between related objects. For example, an object can notify another object without knowing anything about that object.
The implementation logic of the observer design pattern is generally similar, with a map-like structure that stores the corresponding relationship between the listening event and the callback function.
In the EventEmitter class, events and their corresponding listeners are stored as key-value pairs. You may wonder why creating a simple key-value pair is so complicated, and why not just use this._events = {};
.
Indeed, the initial implementation in the community was like this, but with the upgrade of V8 and the increasing support for ES6, the implementation method is to use an empty constructor and pre-set the prototype of this constructor to null.
Through jsperf comparison of the two implementations, we found that this implementation is twice as fast as the simple implementation!
addListener: Add event listener, on: Alias of addListener, they are actually the same.
When using in complex scenarios, there may be a need for callback order. L250, the default is to add the listener to the end of the event listener array. L247-L248, the prepend
flag indicates whether to add to the front of the event array.
Learn more at https://github.com/nodejs/node/pull/6032
In the implementation of the EventEmitter#removeListener API, we need to remove an element from the stored listener array. Our first thought is to use the Array#splice API, i.e. arr.splice(i, 1). However, this API provides too much functionality, supporting the removal of a custom number of elements and the addition of custom elements to the array. Therefore, the source code chooses to implement the minimum usable one:
The performance is 1.5 times faster than the native call.
When an event is triggered, the number of arguments that the listener has is arbitrary.
Convert function calls with variable parameters into fixed-parameter function calls, and support up to three parameters. If there are more than 3 parameters, 'emitMany' is called. The result is self-evident, let's still compare how much worse it will be, taking three parameters as an example: jsperf shows a performance gap of about 1x.
Learn more at https://github.com/iojs/io.js/pull/601
L1410, FSWatcher object inherits EventEmitter, which gives it access to EventEmitter's methods. L1404, When an error occurs in the underlying system, a notification event 'error' is emitted. L1406, When a file changes, the FSWatcher object emits a 'change' event, with the specific change identified by event and the filename indicating the name of the file.
L1396, The method 'onchange' attached to the 'FSEvent' object serves as a callback for C++ to call Javascript, with different implementation methods on different platforms. We will discuss this in detail in the file system chapter.
The above is the implementation of file change monitoring in the fs module, which exports the API: fs.watch()
for external use, as well as fs.watchFile()
. Let's take a look at the official documentation:
then the callback function is added to the observers of the 'change' event by default. Of course, you can also use a different approach, such as:
This allows for chain calls, which is in line with the currently popular Reactive Programming paradigm. The RP programming paradigm improves the abstraction level of coding, allowing you to better focus on the relationship between various events in business logic, avoiding a large number of trivial and tedious implementations, making coding more concise.
Let's take a look at how readline handles keyboard input, which involves a complex state machine and event sending, making it a great example for learning the event module.
If no specific query is set in advance and a specified callback is triggered after the user responds, the Interface
object will trigger the line
event. This event is triggered when the input stream receives a , usually when the user hits enter or return. It is a powerful tool for listening to user input. An example of listening to the line
event is:
This module also handles composite function keys, such as Ctrl + c and Ctrl + z. Let's analyze the code for Ctrl + c:
L681-L682, Ignore the ESC
key.
L684, First, determine if the Ctrl and Shift composite keys are pressed at the same time. If so, L685-L694 are processed first.
L696, If the Ctrl key is pressed, continue to judge at L699. If the other is c
, the object is closed by default.
L701, If there are external observers, send the SIGINT
event to be handled by the observer.
A Read-Eval-Print-Loop (REPL) can be used for standalone programs or easily integrated into other programs. The REPL provides an interactive way to execute JavaScript and view output. It can be used for debugging, testing, or just trying something out.
When you execute node without any parameters in the command line, you will enter the REPL. It provides a simple Emacs line editor.
REPLServer inherits from Interface, as shown in the code: inherits(REPLServer, rl.Interface);
It listens for the line event and customizes keywords to support interactive commands.
Let's take a look at the code implementation:
L400, Enable debugging by setting the environment variable NODE_DEBUG=REPL.
L407, Parse the input cmd and handle regular expressions.
L412, Check if the input starts with a .
and is not a floating point number. If so, use regular expressions to match the string.
For example, for .help
, matches
will be [ '.help', 'help', '', index: 0, input: '.help' ]
, where keyword is help
and rest is an empty string.
L416, Find the corresponding method from the commands
object using the keyword and execute it.
An example of a REPL instance running on curl(1) can be found here: https://gist.github.com/2053342
EventEmitter
Can notify multiple listeners
Generally called multiple times.
Callback
Can notify at most one listener
Usually called once, regardless of whether the operation is successful or not.
The Event module is a typical application of the Observer design pattern. It is also the essence of Reactive Programming.
[1].https://segmentfault.com/a/1190000005051034
EventEmitter allows us to register one or more functions as listeners, which are called when a specific event is triggered. As shown in the following diagram: