preface
The original address
In recent days smallpox quite long time in koA (1) of the source code analysis above, the first time to see, was middleware implementation of that whole dizzy, completely do not know so, look again, as if to understand what, again and again, I go, is god, is in tears, is crazy!!
In the front
The following example prints some information in the console (what exactly? Guess 😀), and return Hello World.
let koa = require('koa')
let app = koa()
app.use(function * (next) {
console.log('generate1----start')
yield next
console.log('generate1----end')
})
app.use(function * (next) {
console.log('generate2----start')
yield next
console.log('generate2----end')
this.body = 'hello world'
})
app.listen(3000)Copy the code
Those of you who have used KOA know that the way to add middleware is to use the KOA instance’s use method and pass in a generator function that accepts a next. It will not be explained here, but will be explained in detail later).
Execute use dry
This is the constructor for KOA, so I’m going to remove some code that I don’t need at the moment, so let’s focus on the Middleware array.
function Application() {
// xxx
this.middleware = []; // This array is used to hold the middleware
// xxx
}Copy the code
Now we’re going to look at the use method
Also removing some temporarily unused code, you can see that each time the use method is invoked, a generator function passed in is pushed into the Middleware array
app.use = function(fn){
// xxx
this.middleware.push(fn);
// xxx
};Copy the code
All right! You already know that koA preempts an array of middleware that requests may pass through through the use method.
This brings us to the focus of this article: when a request arrives, how does it pass through the middleware and run
The following callback function is the callback to be executed when the request comes in (again, try to eliminate unnecessary code).
app.callback = function(){
// xxx
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
// xxx
return function(req, res){
// xxx
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
// xxx}};Copy the code
This code can be broken into two parts
- The pre-request middleware initialization processing part
- The part of the middleware that runs when the request arrives
Let’s break it down into parts
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));Copy the code
This code checks experimental that koA will support incoming async if it is set to true, otherwise co. Wrap (compose(this.middleware)).
One line to initialize the middleware?
I know KOA is cool, but don’t be cool, so a good programmer is not judged by the amount of code
So what’s the magic about this code
compose(this.middleware)Copy the code
What does compose do when you pass in an array of middleware middleware parameters to the compose method? In fact, it is to have no relationship with each of the middleware to the head and tail string up, so there are thousands of links between them.
function compose(middleware){
return function* (next){
// The first time we get next is due to the generator object generated by *noop
if(! next) next = noop();var i = middleware.length;
// Run generator functions in Middleware from back to front
while (i--) {
// Pass the generator object from the latter middleware to the former as the first argument
next = middleware[i].call(this, next);
}
return yield*next; }}function *noop(){}Copy the code
Compose starts processing the middleware from the last one and works its way up to the first one. The key is to pass the latter middleware a generator object as a parameter (which is next at the beginning of this article, which is a generator object) to the former middleware. Of course, the last middleware argument, next, is an empty object generated by a generator function.
Compose compose compose compose compose Compose Compose Compose Compose Compose compose compose compose compose compose compose compose compose
function * gen1 (next) {
yield 'gen1'
yield * next // Start executing the next middleware
yield 'gen1-end' // Proceed with gen1 middleware logic after the next middleware execution completes
}
function * gen2 (next) {
yield 'gen2'
yield * next // Start executing the next middleware
yield 'gen2-end' // Proceed to gen2 middleware logic after the next middleware execution completes
}
function * gen3 (next) {
yield 'gen3'
yield * next // Start executing the next middleware
yield 'gen3-end' // Proceed to gen3 middleware logic after the next middleware execution completes
}
function * noop () {}
var middleware = [gen1, gen2, gen3]
var len = middleware.length
var next = noop() // A parameter to provide to the last middleware
while(len--) {
next = middleware[len].call(null, next)
}
function * letGo (next) {
yield * next
}
var g = letGo(next)
g.next() // {value: "gen1", done: false}
g.next() // {value: "gen2", done: false}
g.next() // {value: "gen3", done: false}
g.next() // {value: "gen3-end", done: false}
g.next() // {value: "gen2-end", done: false}
g.next() // {value: "gen1-end", done: false}
g.next() // {value: undefined, done: true}Copy the code
See? The order in which the middleware is strung together is
gen1 -> gen2 -> gen3 -> noop -> gen3 -> gen2 -> gen1
Thus end to end, and then the relationship 😈.
co.wrap
A generator function is returned after the compose processing.
co.wrap(compose(this.middleware))Copy the code
All of the above code can be understood as
co.wrap(function * gen ())Copy the code
Ok, so let’s see what Co. Wrap has done to get closer and closer
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this.arguments)); }}Copy the code
As you can see, the co.wrap returns a normal function called createPromise, which is fn at the beginning of this article.
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));Copy the code
The middleware is running
Having said that, how the middleware is initialized, that is, if it goes from irrelevant to closely related, let’s start with how the initialized middleware behaves when the request comes in.
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);Copy the code
This section is the middleware execution that the request handler will pass through. Fn returns a Promise after execution, and KOA handles the request separately by registering successful and failed callback functions.
Let’s go back to
co.wrap = function (fn) {
// xxx
function createPromise() {
return co.call(this, fn.apply(this.arguments)); }}Copy the code
The fn in createPromise is a generator that is returned by compose’s middleware, so the result is a generator object that will be passed to the classic CO. For example, “Compose” is composed’s generator object, which is composed’s generator object. For example, “Compose” is composed’s generator object
Let’s go back to CO
function co(gen) {
var ctx = this;
var args = slice.call(arguments.1)
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
if(! gen ||typeofgen.next ! = ='function') return resolve(gen);
onFulfilled();
/** * @param {Mixed} res * @return {Promise} * @api private */
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
/** * @param {Error} err * @return {Promise} * @api private */
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
/** * Get the next value in the generator, * return a promise. * * @param {Object} ret * @return {Promise} * @api private */
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"')); }}); }Copy the code
This will be a big pity. When I enter co for the first time, I will directly perform ondepressing () because it is already a generator object.
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}Copy the code
Gen. Next is the business logic used to execute the middleware, and when yield statements are encountered, the next result is returned and assigned to RET, which is usually next, the next middleware of the current middleware.
Get the next piece of middleware and hand it over to next
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}Copy the code
When the middleware execution ends, the Promise state is set to success. Otherwise, the RET (that is, the next middleware) is used again with the CO package. Just look at these lines of toPromise
function toPromise(obj) {
// xxx
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
// xxx
}Copy the code
Note that the return value of toPromise at this time is a Promise, which is crucial for the next middleware execution to continue after the last middleware interrupted execution
function next(ret) {
// xxx
var value = toPromise.call(ctx, ret.value);
// That is, the Promise implementation returned by the previous toPromise. When the latter middleware execution ends, the implementation is reverted to the previous middleware interrupt and continues
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// xxx
}Copy the code
Seeing this, we can conclude that almost koA’s middleware will be wrapped by CO once, and each middleware can monitor whether the subsequent middleware ends through the Promise’s THEN, and the latter middleware will perform the operation monitored by the former middleware with THEN. This action is to execute the code following yield Next of the middleware
For example:
When a request is received in KOA, the request goes through two middleware, middleware 1 and Middleware 2,
Middleware 1
// Middleware 1 before yield middleware 2
yieldThe middleware2
// Continue to execute middleware 1 code after middleware 2 completes executionCopy the code
Middleware 2
Middleware 2 code before yield Noop middleware
yieldIt middleware// Continue to execute middleware 2 code after noOP middleware execution is completeCopy the code
Then the processing process is that CO will immediately call onFulfilled to execute the first half of middleware 1 code, encounteredYield Middleware 2
(we mean the empty NOOP middleware), and so on until the last middleware (in this case, the empty NOOP middleware) completes, and the promise’s resolve method is called to terminate, ok, At this time, middleware 2 will listen to the end of noOP execution, and immediately perform ondepressing to execute the last half of the yield NOOP middleware code. Well, middleware 2 will also call the promise’s resolve method immediately to indicate the end, OK, At this time, middleware 1 listens until the execution of middleware 2 ends, and immediately performs onFulfilled to perform the last half of the code of yield middleware 2. Finally, the middleware is fulfilled all, and then performs respond.Call (CTX).
Ah ah ah round, but slowly look, carefully think, or can figure out. Writing this process in code is somewhat similar
new Promise((resolve, reject) = > {
// I am middleware 1
yield new Promise((resolve, reject) = > {
// I am middleware 2
yield new Promise((resolve, reject) = > {
/ / I am a body
})
// I am middleware 2
})
// I am middleware 1
});Copy the code
At the end
Rory said a lot of wordy, also do not know whether the implementation of the principle of clear.
If it helps you understand KOA, click on the source address and click on the star
If it helps you understand KOA, click on the source address and click on the star
If it helps you understand KOA, click on the source address and click on the star
The source address