In my previous interview with Tencent, the interviewer asked me a question, “Do you know koA’s Onion model?”

I froze for a moment, “I don’t know, I don’t know.”

Although I was interviewing for a front-end internship, the interviewer kept asking me the back-end questions from the beginning to the end, which I didn’t quite understand. However, since we were asked this question, we must be able to talk to the interviewer about it the next time we are asked

So I went and read koA’s source code and wrote this article

This article is mainly about the principles of the Onion model, and nothing else is covered. I assume that the reader has used KOA

Onions

To start with, I created an app and added three pieces of middleware

const Koa = require('koa');

const app = new Koa();

app.use(async (ctx, next)=>{
    console.log(1)
    await next();
    console.log(1)}); app.use(async (ctx, next) => {
    console.log(2)
    await next();
    console.log(2)
})
app.use(async (ctx, next) => {
    console.log(3)
})

app.listen(3000);
Copy the code

After accessing localhost:3000, the console output looks like this

1
2
3
2
1
Copy the code

This phenomenon can be illustrated by the following graph

Let me put a shell on him

Does it look like an onion?! ?

Yes, this is koA’s onion model

His execution process as shown in the figure, from the outside layer by layer into, and then layer by layer from the inside out, this is also the execution process of KOA middleware, BELIEVE that see here, you should already know the koA middleware execution process, the following is the implementation code of the Onion model

The principle of

In koA’s source code, the middleware is stored in an array

let middleware = [];
Copy the code

We can use(…) Add middleware, the main logic goes something like this

const use = (fn) = >{
  middleware.push(fn)
}
Copy the code

The middleware call is compose, which I explain in detail below

function compose (middleware) {
  return function (context, next) {
    // index is used to record the number of middleware currently executed
    let index = -1
    return dispatch(0)
    // what I does: the subscript of the middleware currently being executed
    function dispatch (i) {
      // If I <=index means this is the second time next is executed, throw an exception
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      // Update index, corresponding to above
      index = i
      // Extract the current middleware that needs to be executed
      let fn = middleware[i]
      // Execute the compose callback function
      if (i === middleware.length) fn = next
      // If it does not exist, it will return directly to the last middleware
      if(! fn)return Promise.resolve()
      // Try is used to ensure that an exception can be passed and returned
      try {
        // Pass the dispatch as the next function of the middleware, forming a recursive call
        // The next middleware is called
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))}catch (err) {
        return Promise.reject(err)
      }
    }
  }
}
Copy the code