koa onion model

Keywords: node.js

Analysis

1. First, this is the easiest example to get started with koa2. I will show you the onion model of koa2 with this example.

const Koa = require('koa');
const app = new Koa();
app.use((ctx,next)=>{
    console.log("First Middleware Execution");    
   next() ;
});

// Second Middleware
app.use((ctx,next)=>{
    console.log("Second Middleware");
})

let r = app.listen(8080);
console.log("server run on 8080");

In this case, app first calls use twice, then listens on the port.

listen(...args) {
    debug('listen');
    // callback will be called when the client sends the request
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }

So use is the core:

 use(fn) {
    // ...some judgment codes
    this.middleware.push(fn);
    return this;
  }

You can see from the above that the function of the external use r is recorded through an internal middleware variable, and then it is gone.

OK, now when the client sends the request, the context object is created internally and the request is processed:

callback() {
    const fn = compose(this.middleware);

    if (!this.listenerCount('error')) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
      // Create Context
      const ctx = this.createContext(req, res);
      // Processing Request
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }

Processing Request

handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    // Core, call middleware, from which we can see that FN in use(fn) is a promise
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

OK, here we know that when the browser sends a request, koa's application object calls compose based on middleware to generate another function fn, which is then executed by passing in the context ctx to fn.As we all know, if the top order of code execution is to print the first middleware execution and then the second, then this compose function needs to guarantee this mechanism.How to achieve this is as follows:

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

The code above, while small, is the most important part of koa2's onion model implementation.The whole process is as follows:

  1. The client sends a request and calls fn(ctx), where next is empty=>dispatcher (0), and gets the first use's function (middleware[0]) to execute the function with the following parameters: ctx, dispatch(1), which is next in the first use; execute next();
  2. Executing next() in the method body of the first use is equivalent to executing dispatcher(1), taking the function of the second use (middleware[1]), then executing the function with the following parameters: ctx, dispatch(2), and so on.

summary

Although there are few koa2 source codes, the principle is clever and worth learning. Just because of its small size, learning from the source code is also easier for w.

Posted by savj14 on Fri, 24 May 2019 09:36:04 -0700