koa

Koajs: Next Generation Web Framework for Node.js.

Koa is the next generation Web development framework based on Node.js. It is based on middleware mechanism elegant, concise, strong expression, high degree of freedom of simple easy to use Web framework. A web application with specific functions can be realized by freely combining the middleware with various independent functions. For example, the routing function is realized through KOA-Router middleware, and cross-domain function is realized through KOA-CORS. One of these middleware components is koA-Compose, and it is the use of this middleware that leads to the well-known Onion model in KOA.

The onion model

The Onion model is the serial control flow of KOA middleware.

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  console.log('enter first middleware');
  await next();
  console.log('out first middleware');
});
app.use(async (ctx, next) => {
  console.log('enter second middleware');
  await next();
  console.log('out second middleware');
});
app.use(async (ctx, next) => {
  console.log('enter third middleware');
  await next();
  console.log('out third middleware');
});

app.listen(3000);
Copy the code

The output is as follows:

koa-compose

Koa – compose the source code

As shown in the code above, the middleware inputs for use are CTX and next, where CTX is the context object in KOA, and what is next? With this question in mind let’s read the source code for Koa-compose.

'use strict'

/** * Expose compositor. */

module.exports = compose

/** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array! ')
  for (const fn of middleware) {
    if (typeoffn ! = ='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)
      }
    }
  }
}
Copy the code

The koa-compose entry parameter is the middleware array and the return is an anonymous middleware function.

function compose (middleware) {
	// The type check input must be an array
	if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array! ')
	// The Middleware array must contain middleware functions
  	for (const fn of middleware) {
    	if (typeoffn ! = ='function') throw new TypeError('Middleware must be composed of functions! ')}Return an anonymous middleware function
  return funxtion (ctx, next) {}
}
Copy the code

Let’s see what the anonymous function for return is:

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)
     }
   }
}
Copy the code

Let’s get rid of the judgment condition and look at what’s at the bottom, right

return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
Copy the code

This gives us an idea of what the next function really is, which is dispatch.bind(null, I + 1). It is also through Dispatch that control is handed over to the next middleware. In use, await next() formally transfers control to the next middleware, first => second =>… => last one, when the last middleware execution is completed, at this time, the stack starts to execute the current top execution environment out of the stack, the last one => second to last =>… => First one. So you have the onion model.