Series of articles:
- Read one NPM module (1) – username every day
- Read one NPM module (2) – MEM every day
- Read one NPM module per day (3) – Mimic-FN
- Read one NPM module per day (4) – Throttle-debounce
A one-sentence introduction
The module we read today is EE-First, through which we can listen to a series of events and know which event occurs first and perform corresponding operations. The current package version is 1.1.1, with about 4.3 million downloads per week.
usage
Ee-first is an acronym for EventEmitter. Many objects in Node.js are derived from it. Net. Server | fs. ReadStram | stream, etc., can say many core API are event driven by EventEmitter, its use is very simple, mainly emit (emit events) and on two interface (surveillance) :
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('sayHi', (name) => {
console.log(`hi, my name is ${name}! `);
});
emitter.emit('sayHi'.'Elvin');
// => 'hi, my name is Elvin! '
Copy the code
Here’s how ee-frist is used:
const EventEmitter = require('events');
const first = require('ee-first');
// 1. Listen for the first event to occur
const ee1 = new EventEmitter();
const ee2 = new EventEmitter();
first([
[ee1, 'close'.'end'.'error'],
[ee2, 'error']],function (err, ee, event, args) {
console.log(` '${event}' happened! `);
})
ee1.emit('end');
// => 'end' happened!
// 2. Unbind the listener event
const ee3 = new EventEmitter();
const ee4 = new EventEmitter();
const trunk = first([
[ee3, 'close'.'end'.'error'],
[ee4, 'error']],function (err, ee, event, args) {
console.log(` '${event}' happened! `);
})
trunk.cancel();
ee1.emit('end');
// => Output nothing
Copy the code
The source code to learn
Parameter calibration
The verification of parameters in the source code is mainly through array.isarray () to determine whether the parameter is an Array, if not, by throwing an exception to give a prompt message – for third-party modules, need to keep a distrust of callers, so the verification of parameters is very important.
In earlier years, JavaScript did not support the array.isarray () method, It was through the Object. The prototype. ToString. Call (someVar) = = = ‘[Object Array]’ to determine whether someVar Array. Of course, now that it’s 2018, there’s no need to use these tricks.
/ / source 5-1
function first (stuff, done) {
if (!Array.isArray(stuff)) {
throw new TypeError('arg must be an array of [ee, events...] arrays')}for (var i = 0; i < stuff.length; i++) {
var arr = stuff[i]
if (!Array.isArray(arr) || arr.length < 2) {
throw new TypeError('each array member must be [ee, events...] ')}// ...}}Copy the code
Generating response function
In EE-FIRST, an event listener is generated for each event name passed in:
/ / source 5-2
/** * Create the event listener. ** @param {String} event For example 'end', 'error' etc. * @param {Function} done, the response Function passed in when calling Ee-first */
function listener (event, done) {
return function onevent (arg1) {
var args = new Array(arguments.length)
var ee = this
var err = event === 'error' ? arg1 : null
// copy args to prevent arguments escaping scope
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i]
}
done(err, ee, event, args)
}
}
Copy the code
There are two caveats:
- right
error
Events are handled specially because node.js passes an error message as the first argument to the callback function if something fails, such as a file read:fs.readFile(filePath, (err, data) => { ... }
. In my opinion, passing an error message as the first argument to the callback function is a highly recommended coding specification to draw attention to the exception message. - through
new Array()
And the loop assignment operation willonevent
The arguments to the function are stored in a new arrayargs
And pass it todone
Function. If low version compatibility is not a concern, the ES6 approach can be used hereArray.from()
Implement this feature. I can’t figure out why I’m doing this. Although the author has commented that it is to prevent parameter scope exceptions, I didn’t think of this scenario. I hope you can point it out in the comments
Binding response function
Next, bind the generated EventEmitter function to the corresponding EventEmitter. The key is var fn = Listener (event, callback); Ee. On (event, fn)
/ / source 5-3
function first (stuff, done) {
var cleanups = []
for (var i = 0; i < stuff.length; i++) {
var arr = stuff[i]
var ee = arr[0]
for (var j = 1; j < arr.length; j++) {
var event = arr[j]
var fn = listener(event, callback)
// listen to the event
ee.on(event, fn)
// push this listener to the list of cleanups
cleanups.push({
ee: ee,
event: event,
fn: fn
})
}
}
function callback () {
cleanup()
done.apply(null.arguments)}// ...
}
Copy the code
Remove response function
In the previous step, I don’t know if you noticed two cleanups:
-
At the beginning of source 5-3, the cleanups array is declared and the event and response functions are stored in a one-to-one correspondence with cleanups.push() each time the response function is bound.
-
In the callback function at the end of source code 5-3, before executing the done() response function, the cleanup() function is called. This function is very simple, which iterates through the Cleanups array and removes the previously bound event listener functions one by one. The reason for this is that binding EventEmitter functions can be expensive (which is why, by default, every EventEmitter can be bound to up to 10 listeners in Node.js).
/ / source 5-4 function cleanup () { var x for (var i = 0; i < cleanups.length; i++) { x = cleanups[i] x.ee.removeListener(x.event, x.fn) } } Copy the code
Thunk function
The last bit of code left unsaid is the shortest and most rewarding — it helps me understand the common concept of thunk.
/ / source 5-5
function first (stuff, done) {
// ...
function thunk (fn) {
done = fn
}
thunk.cancel = cleanup
return thunk
}
Copy the code
The line thunk. Cancel = cleanup is easy to understand, giving the return value of first() the ability to remove all response functions. Thunk = {calcel: cleanup} const thunk = {calcel: cleanup}
This is not mentioned in readme.md until I read the test code written by the author:
// source code 5-6 test code
const EventEmitter = require('events').EventEmitter
const assert = require('assert')
const first = require('ee-first')
it('should return a thunk'.function (testDone) {
const thunk = first([
[ee1, 'a'.'b'.'c'],
[ee2, 'a'.'b'.'c'],
[ee3, 'a'.'b'.'c'],
])
thunk(function (err, ee, event, args) {
assert.ifError(err)
assert.equal(ee, ee2)
assert.equal(event, 'b')
assert.deepEqual(args, [1.2.3])
testDone()
})
ee2.emit('b'.1.2.3)})Copy the code
The above code is a good example of what thunk does: It turns the first(stuff, done) function that takes two arguments into the thunk(done) function that takes only one callback.
Here I quote the definition made by Ruan Yifeng in the meaning and Usage of Thunk function, which I think is very accurate and easy to understand:
In the JavaScript language, the Thunk function replaces a multi-argument function with a single-argument version and accepts only callback functions as arguments.
More broadly, of course, A thunk is A function that wraps an expression to delay its evaluation.
// This code will execute immediately
// x === 3
let x = 1 + 2;
// 1 + 2 is executed only when foo is called
// Foo is a thunk
let foo = (a)= > 1 + 2
Copy the code
This explanation and sample code comes from Redux-thunk – Whtat’s a thunk? .
Write in the last
Ee-first is the most comfortable code I’ve read in days, both fully commented and not overly annotated like the throttle-debounce module I read yesterday.
In addition, when faced with a piece of code does not know what role, you can start to explore through the relevant test code.
About me: graduated from huake, working in Tencent, elvin’s blog welcome to visit ^_^