Speaking of Node, the most commonly used estimates are express and koa, both of which use the concept of middleware, mainly for the unified processing of requests.
koa's request processing is a typical onion model. Here's the official map, and the component of this model is middleware.
Next, let's take a look at the source code of koa to understand how middleware is implemented.
First we found koa's warehouse. Koa Well, I know you'll all do that.
Find the module's entry file application.js in package.json, and browse a little (I have to say, the code of tj God is really beautiful) to find Koa's code for processing requests.
app.callback = function(){
if (this.experimental) {
console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.')
}
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
var self = this;
if (!this.listeners('error').length) this.on('error', this.onerror);
return function handleRequest(req, res){
res.statusCode = 404;
var ctx = self.createContext(req, res);
onFinished(res, ctx.onerror);
fn.call(ctx).then(function handleResponse() {
respond.call(ctx);
}).catch(ctx.onerror);
}
};
After looking at this function, we can see that the function that actually handles the content of the request is fn, and the definition of this function is the following function
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
Well, to be exact, that's it.
co.wrap(compose(this.middleware));
In fact, we only need to know what this function does to know how the middleware works.
At the same time, we found the app.use function in the application. JS file.
app.use = function(fn){
...
this.middleware.push(fn);
return this;
};
From this code, we can see that this.middleware is an array of generator functions.
Next we need to know what the compose function does. We find the compose function, which is actually very short.
function compose(middleware){
return function *(next){
if (!next) next = noop();
var i = middleware.length;
while (i--) {
next = middleware[i].call(this, next);
}
return yield *next;
}
}
function *noop(){}
Here the compose function returns a Generator, so the above code can look like the following (and, of course, in the closure of middleware variables)
co.wrap(function *(next){
if (!next) next = noop();
var i = middleware.length;
while (i--) {
next = middleware[i].call(this, next);
}
return yield *next;
})
Next, let's look at co.wrap.
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
};
So the code can look like this
function createPromise() {
var fn = function *(next){
if (!next) next = noop();
var i = middleware.length;
while (i--) {
next = middleware[i].call(this, next);
}
return yield *next;
}
return co.call(this, fn.apply(this, arguments));
}
Next, let's take a closer look at the code
if (!next) next = noop();
var i = middleware.length;
while (i--) {
next = middleware[i].call(this, next);
}
This code traverses our middleware array and eventually generates a code similar to the following
next = (function*(){
// middleware1
...
yield (function*(){
// middleware2
...
yield (function*(){
// middleware3
...
yield (function *(){
// noop
// NO next yield !
})()
// ...middleware3
})
// ...middleware2
})
// ...middleware1
})()
In fact, you can see the onion model here.
And finally, the next operation, which is actually an iterator object generated by a Generator function, is run by co, similar to the following
co(function*(){
...
yield *next
})
co can self-execute generator s. This basically completes the implementation of middleware.
The eye-catching reader can see that yield * is used here at last instead of yield. It can be executed about Co. In fact, in order to reduce the one run of co, yield *next should be used every time. Maybe the God of tj is afraid that everyone forgot to add it, so it's better to suggest that everyone directly yield next in demo. Specifically, you can see my analysis of the implementation of CO source code. I will mention here that yield * can automatically execute the iterator properties of the following expression, while yield only returns the following expression directly. So a yield * can take co directly to every step of the latter iterator, while yield can only get the iterator, and then recursively call CO to execute the iterator.
Finally, I would like to welcome you to Tucao. Thank you.