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!!

koa

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

  1. The pre-request middleware initialization processing part
  2. 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

Middleware execution order

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