This paper introduces:

  1. How does KOA handle requests
  2. Koa – compose function

1. Let’s start with two pictures

This is a common koA onion diagram

The KOA Web server receives an API request; Need to go through one by one handler (request – > | fn1 | – > | fn2 | – > | fn… | – > response). How to chain the processing functions in sequence. In koA, it is the koa-compose function that is relied on.

As shown in the figure below, a request comes in through the permission check, the Controller layer, the Service layer, and so on, and passes through each layer of functions (handled by the middleware) before the result is returned to the interface caller.

2. Relevant source code

Start with a KOA service, through the following demo, with the source code to see how KOA is handling requests.

const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
  console.log('-- 1 -- -- - >')
  await next()
  console.log('6 = = = = = = >')
})
app.use(async (ctx, next) => {
  console.log('-- -- -- -- - > 2')
  await next()
  console.log('= = = = = = > 5')
})
app.use(async (ctx, next) => {
  console.log('- 3 - >')
  await next()
  console.log('= = = = = = 4 >')
})

app.listen(8899.() = > {
  console.log('Application started')})The result of the execution structure is 1, 2, 3, 4, 5, 6
Copy the code

Take a look at the source code to see how koA handles requests. The following source code is in the koa/lib/application.js directory

1. app.listen()

  listen(. args) {
    const server = http.createServer(this.callback());
    // Call Node's native http.createserver ([requestListener])
    // requestListener is a request handler used in response to a request event;
	// This function takes two arguments, req,res. This. Callback is executed when a request comes in
    returnserver.listen(... args);// Port host information
  }

Copy the code

2. Request handler this.callback()

  callback() {
  // Convert the middleware function to the compose function
    const fn = compose(this.middleware); 
    if (!this.listenerCount('error')) this.on('error'.this.onerror);
	// The function that responds to the request event is called the callback function
    const handleRequest = (req, res) = > { 
    	// Create context according to req, res
      const ctx = this.createContext(req, res); 
      // Eventually the handleRequest method is called, passing in the context and the compose function
      return this.handleRequest(ctx, fn); 
    };
    return handleRequest;
  }
Copy the code

3. Array of middleware functions this.Middleware

 use(fn) {
    if (typeoffn ! = ='function') throw new TypeError('middleware must be a function! ');
    // Add functions to the Middleware array when use is called
    this.middleware.push(fn); 
    return this;
  }

Copy the code

4. Callback function entity this.handlerequest

 handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err= > ctx.onerror(err); // Handle the error
    const handleResponse = () = > respond(ctx); // Process the returned result
    onFinished(res, onerror); 
    Compose: compose compose compose compose compose compose compose compose compose compose
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
Copy the code

The core of the process is to call the compose function and convert the array of middleware functions to the compose function const fn = compose(this.middleware); Fn is then called, which executes all middleware functions in turn; The context CTX traverses all middleware functions.

3. com pose function

First take a look at the koa-compose function source

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

If you remove some of the comments, such as fault tolerance, you can see that the koa-compose function has more than a dozen lines of code

function compose (middleware) {
  return function (context, next) {
    let index = -1
    return dispatch(0) 
    function dispatch (i) {
      index = i
      let fn = middleware[i] 
      if (i === middleware.length) {
        fn = next
      }
      if(! fn)return Promise.resolve()
      return Promise.resolve(fn(context, function next () { 
        return dispatch(i + 1)}))}}Copy the code

See the koa-compose function in action with the following four simple functions

async function a (context,next) {
  console.log('1. Before function [a] next - execute..... ')
  await next()
  console.log('8. After function [a] next - execute..... ')}async function b (context, next) {
  console.log('2. Before function [b] next - execute..... ')
  await next()
  console.log('7. After function [b] next - execute..... ')}async function c (context, next) {
  console.log('3. Before function [c] next - execute..... ')
  await next()
  console.log('6. After function [c] next - execute..... ')}async function d (context, next) {
  console.log('4. Function [d] next before - execute..... ')
  console.log('5. After function [d] next - execute..... ')}Copy the code

Viewing the Result

var composeMiddles1 = compose([a,b,c,d])
composeMiddles()
// Execution result
// 1. Before function [a] next -.....
// 2. Before function [b] next - execute.....
// 3. Before function [c] next - execute.....
// 4. Function [d] next before - execute.....
// 5. After function [d] next - execute.....
// 6. After function [c] next - execute.....
After function [b] next - execute.....
After function [a] next - execute.....
Copy the code

To visualize this, we use the following pseudocode

async function a (context,next) {
  console.log('1. Before function [a] next - execute..... ')

  async function b (context, next) {
    console.log('2. Before function [b] next - execute..... ')

    async function c (context, next) {
      console.log('3. Before function [c] next - execute..... ')

      async function d (context, next) {
        console.log('4. Function [d] next before - execute..... ')
        console.log('8. After function [d] next - execute..... ')}console.log('7. After function [c] next - execute..... ')}console.log('6. After function [b] next - execute..... ')}console.log('5. After function [a] next - execute..... ')}Copy the code

4.compose function analysis


// An array of middleware functions
function compose (middleware) { 
  return function (context, next) { // Calling the compose function returns a function
    let index = -1
    return dispatch(0) // Starts the first function in the Middleware array
    function dispatch (i) {
      index = i
      let fn = middleware[i]
      if (i === middleware.length) {
        fn = next
      }
      if(! fn)return Promise.resolve()
      // this is to execute a, b,c,d functions and await next()
      return Promise.resolve(fn(context, function next () { 
        return dispatch(i + 1)  // Execute the next middleware function}}}}))ComposeMiddles1 is a function that contains all the middleware functions passed in and can execute them in sequence
var composeMiddles1 = compose([a,b,c,d]) 
composeMiddles() // Execute dispatch(0)

dispatch(0) // Start the execution of the first function (a)

// await next() in a function
async function a (context,next) {
  console.log('1. Before function [a] next - execute..... ')
  await next() Function next () {return dispatch(I + 1)}
  console.log('8. After function [a] next - execute..... ')}await next()  
// It is executed
dispatch(i + 1) 

// dispatch(1) is the b function

// In function b
await next()

/ /...
// This will be executed in sequence

Copy the code