For example: g() + H () => g(h())). For koa-compose, the middleware of koA/KOA-router is composed and next() is combined to form an onion model.

Onion model execution order

We created the KOA application as follows:

const koa = require('koa');
const app = new koa();
app.use((ctx, next) => {
 console.log('First middleware function')
 await next();
 console.log('After the first middleware function next');
})
app.use(async (ctx, next) => {
 console.log('Second middleware function')
 await next();
 console.log('Second middleware function after Next');
})
app.use(ctx => {
 console.log('response');
 ctx.body = 'hello'
})
​
app.listen(3000)
Copy the code

The above code can be started using Node text-next-js and can be accessed in a browser at http://localhost:3000/

After access, the following values will be printed in the command window that starts:

The first middleware function the second middleware function responds to the second middleware function next after the first middleware function next

Note: When app.use is used to add the given middleware to the application, the middleware (which is really just a function) takes two parameters: CTX and next. Where next is also a function.

Koa – compose the source code

Before diving into the koA-compose source code, let’s take a look at how compose is called in the KOA source code. Refer to the previous article for details.

listen(... args) { debug('listen');
   const server = http.createServer(this.callback());
   returnserver.listen(... args); }callback() {// compose returns fn const fn = compose(this.middleware);if(! this.listenerCount('error')) this.on('error', this.onerror); const handleRequest = (req, res) => { const ctx = this.createContext(req, res); // Create a CTX objectreturnthis.handleRequest(ctx, fn); // Pass fn to this.handlerequest};returnhandleRequest; } handleRequest(ctx, fnMiddleware) { const res = ctx.res; res.statusCode = 404; onFinished(res, onerror); // We're going to hazard a guess that the return value of the compose function is onefunction. And thefunctionThe return value of "is a Promise object. // To be verified by the following source code.return fnMiddleware(ctx)
   .then(() => respond(ctx))
   .catch(err => ctx.onerror(err));
}
Copy the code

The callback function is executed at app.listen, which uses Node’s native HTTP module to build the HTTP server and handles the middleware logic while the server is being created.

Now that we know how KOA calls compose, take a look at the koA-compose source code. Koa-compose’s code is less than 50 lines long, which is a pretty neat piece of code to peruse, but the actual core code is this:

module.exports = compose
​
functionCompose (middleware) {// The middleware arguments passed in must be arraysif(! Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array! '// The elements of the Middleware array must be functionsfor (const fn of middleware) {
   if(typeof fn ! = ='function') throw new TypeError('Middleware must be composed of functions! '} // returns a function closure that keeps a reference to middleware. For example, the compose function returns a promise object. For example, the compose function returns a promise object. Further verifies the above speculation.return function (context, next) {
   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, function next () {
         return dispatch(i + 1)
       }))
     } catch (err) {
       return Promise.reject(err)
     }
   }
  }
}
Copy the code

It is short, but there are four layers of return, which can be confusing at first glance. We only look at the third and fourth layers of return, which return the actual execution chain.

return Promise.resolve(fn(context, function next () {
   return dispatch(i + 1)
}))
Copy the code

**fn = middleware[I]** This code traverses the array of middleware fn and executes:

fn(context, function next () {
   return dispatch(i + 1)
})
Copy the code

Here you can see two parameters passed to the middleware: context and the next function.

As mentioned earlier, when app.use is used to add the given middleware to the application, the middleware (which is really just a function) takes two parameters: CTX and next. Where next is also a function.

You can see why there are two parameters when registering middleware.

Next, let’s look at what’s going on with the Onion model. Consider the first middleware in the previous example:

app.use((ctx, next) => {
 console.log('First middleware function')
 await next();
 console.log('After the first middleware function next');
})
Copy the code
  • The first time, the first middleware is called, dispatch(0), and expands:
Promise.resolve(((ctx, next) => {
   console.log('First middleware function')
   await next();
   console.log('After the first middleware function next');
})(context, function next () {
   return dispatch(i + 1)
})));
Copy the code

First execute console.log(‘ first middleware function ‘) and the log will be fine.

Next, watch out for Old Tie! Watch out for Old Tie! Watch out for Old Tie! It’s so important that it should be repeated for three times. Before executing to **await next(); **, return dispatch(I + 1).

If you look at the dispatch function above, you can see that this is recursive to the second middleware, i.e. the second log is not executed at all: console.log(‘ after the first middleware function next ‘); “To the second middleware.

  • The second time, the second middleware is called, dispatch(1), and expands:
Promise.resolve((ctx, next) => Promise.resolve((ctx, next) => s{
 console.log('First middleware function')
 await Promise.resolve(((ctx, next) => {
   console.log('Second middleware function')
   await next();
   console.log('Second middleware function after Next');
 })(context, function next () {
   return dispatch(i + 1)
 })));
 console.log('After the first middleware function next');
});
Copy the code

The next thing, as you’ve probably guessed, is to await next() in the second middleware; , will also be rotated to the third middleware, and so on, until the last middleware.

conclusion

The middleware model is very easy to use and simple, and even shines on the KOA framework, but it has its own disadvantages, that is, if the middleware array becomes too large, the performance will degrade, so we need to make the best choice for our own situation and business scenario.

Reference article:

Delegates NPM contra analysis of REdux, KOA, Express middleware