Recently, I have been using KOA a lot for server-side development, and I fell in love with koA’s Onion model, which is so useful. And koA is very lean, not a lot of integration, everything has to be loaded on demand, which is more to my liking lol.

In contrast to Express middleware, which uses tandem, one after the other like a sugar gourd, KOA uses a V-shaped structure (onion model), which gives our middleware more flexibility.

Because of my enthusiasm for the Onion model, I took a look into koA’s Onion model. Both the middleware of KOA1 and KOA2 are written based on KOA-compose, and the implementation of this V-structure comes from KoA-compose. Attached source code first:

function compose (middleware) {
  // The middleware parameter is an array of middleware that we hold in tandem with app.use()
  // Check whether the middleware list is an array. If not, throw a type error
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array! ')
  // Determine whether the middleware is a function. If not, throw a type error
  for (const fn of middleware) {
    if (typeoffn ! = ='function') throw new TypeError('Middleware must be composed of functions! ')}/** 1. @param {Object} context 2. @return {Promise} 3. @api public */
  
  return function (context, next) {
    // Next refers to the central function of the Onion model
    Context is a configuration object that holds some configuration. You can also use context to pass some parameters down the middle
     
    // last called middleware #
    let index = - 1  // index is the index of the middleware that records execution
    return dispatch(0)  // Execute the first middleware and recursively call the next middleware through the first middleware
    
    function dispatch (i) {
      // This ensures that a next () is not called more than once in the same middleware
      // When next() is called twice, I is less than index, and an error is thrown
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i] // Fetch the middleware to execute
      if (i === middleware.length) fn = next  // If I is equal to the length of the middleware, that is, the center of the Onion model (the last middleware)
      if(! fn)return Promise.resolve()  // If the middleware is empty, resolve
      try {
        // Recursively execute the next piece of middleware (this will be the focus below)
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}
Copy the code

If you look at this, if the ones below make sense, then you don’t have to look at the ones below, or if you don’t make sense, just move on and analyze it in a little bit more detail.

  1. First, we add middleware with app.use(). In koa’s source code, app.use() is a way to push middleware into the middleware list. Source code is written like this (this is relatively simple not to do analysis) :
 use(fn) {
    if (typeoffn ! = ='function') throw new TypeError('middleware must be a function! ');
    if (isGeneratorFunction(fn)) {
      deprecate('Support for generators will be removed in v3. ' +
                'See the documentation for examples of how to convert old middleware ' +
                'https://github.com/koajs/koa/blob/master/docs/migration.md');
      fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || The '-');
    this.middleware.push(fn);
    return this;
  }
Copy the code

The compose method passes in a list of middleware methods that we add with use(). It determines whether the list is an array and the middleware is a method, and throws a type error if it isn’t.

  1. Compose returns a function that uses closures to cache the list of middleware components. The function then receives two parameters, the first being context, which is an object that holds configuration information. The second parameter is a Next method, which is the center of the Onion model or the inflection point of the V-shaped model.
  2. Create an index variable to hold the middleware index of execution, and then start the recursive execution from the first middleware.
      let index = - 1
      return dispatch(0)
Copy the code
  1. The dispatch method is to execute the middleware and first judge the index. If I is less than index, it means that the next function has been executed twice or more in the same middleware. If I >index, it means that the middleware has not been executed yet
        if (i <= index) return Promise.reject(new Error('next() called multiple times'))
        index = i
Copy the code
  1. Take out the middleware, if I is equal to the long graph of the middleware, it means that the implementation has reached the center of the Onion model, then the last middleware, if the middleware is empty, then resovle is directly dropped
        let fn = middleware[i]
        if(i === middleware.length){
          fn = next
        }
        if(! fn){return Promise.resolve()
        }
Copy the code
  1. Resolve is also a Promise object, because when we await next(),await is the completion of an async function, Async returns a promise object by default, so return a Promise object. We await mext() next() in each middle which means the next middleware, i.e
fn(context, function next () {
            return dispatch(i + 1)})Copy the code

Resolve (fn(context, function next () {XXXX}))) So even though dispatch(0) is only executed initially, this function forms a chain of execution. Take three middleware executions for example, which are formed after dispatch (0) execution:

Promise.resolve( // The first middleware
  function(context,next){  // The next second middleware is called dispatch(1)
     // await code on next (middleware 1)
    await Promise.resolve( // Second middleware
      function(context,next){  // The next second middleware is called dispatch(2)
          // await code on next (middleware 2)
        await Promise.resolve( // The third middleware
          function(context,next){  // The next second middleware is called dispatch(3)
             // await code on next (middleware 3)
            await Promise.resolve()
            // await next code (middleware 3)})// await next code (middleware 2)})// await next code (middleware 2)})Copy the code

An Onion model is formed by executing code above await and then waiting for the final middleware resolve to be passed up one by one. Finally, attach the test code:

async function test1(ctx, next) {
    console.log('On middleware 1');
    await next();
    console.log('Middleware 1 under');
  };
  
  async function test2(ctx, next) {
    console.log('On middleware 2');
    await next();
    console.log('Middleware 2 under');
  };
  
  async function test3(ctx, next) {
    console.log('On middleware 3');
    await next();
    console.log('Middleware 3 under');
  };
  let middleware = [test1, test2, test3];
  
  let cp = compose(middleware);
  
  cp('ctx'.function() {
    console.log('center');
  });
Copy the code

OK, here is koA2 middleware core (KOa-compose) analysis completed, at the beginning of the look, also was a long time around, more times more analysis step by step smooth. – KoA1 middleware will be updated in a few days, koA1 is based on generator and the source code is simpler than KOA2.

Recently I have been looking at koA2 source code, and I will continue to update some koA source code analysis when I have time.

Front end pupil (fresh graduate), if have mistake or other idea. Welcome to correct the exchange ~