This blog is a summary of personal learning Koa. If you have any misunderstanding, please leave a comment or submit an issue on GitHub to correct it. Please indicate the source for reprinting.

Introduction of Koa

Koa is a new Web framework, built by the same people behind Express, that aims to be a smaller, more expressive, and more robust cornerstone of web application and API development. By making use of async functions, Koa helps you discard callback functions and greatly enhances error handling. Koa does not bundle any middleware, but rather provides an elegant way to help you write server-side applications quickly and happily.

Koa middleware functions


The middleware function is to access request object (request), response object (response), and call the next middleware function through next in the application’s request-response cycle. In layman’s terms, use this feature to process request before next and response after next. Koa’s middleware model makes it very easy to implement post-processing logic.



The picture shows the onion model:





The application

Examples from the following sources (Koa website: Hello World app)

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

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
Copy the code

Now, let’s take a closer look at how middleware works.

Middleware Application Cases

Middleware application case (demo for short), the demo code is as follows:

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

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

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

app.use(async (ctx, next) => {
  ctx.body = 'Hello, Koa';
});

app.listen(3001);
Copy the code

Start the server on port 3001. For each client request, the server prints 1, 3, 4, and 2 in sequence. Step by step analysis middleware principle with demo.

Middleware Principles

Registering middleware functions

The demo uses the use registry middleware function. Take a look at the use implementation within the Koa source code.

use(fn) {
  // omit some code...
  this.middleware.push(fn);
  return this;
}
Copy the code

This.middleware.push(fn) is at the heart of the use function without some checksum conversion code. The middleware functions registered are cached in the middleware stack and this itself is returned to facilitate chain calls. Demo registers three middleware functions. When and how these middleware functions are executed is explained below.

Creating a Server Service

listen(. args) {
  const server = http.createServer(this.callback());
  returnserver.listen(... args); }Copy the code

Internally, the Node native HTTP module is used to createServer instances through createServer and listen for specified port numbers. Http.createserver (RequestListener) accepts a RequestListener function as a parameter. So a call to this.callback() returns the RequestListener function. The RequestListener function takes two parameters: request and response.

Callback creates a RequestListener RequestListener function

callback() {
  // Compose is the core of the middleware
  const fn = compose(this.middleware);

  // handleRequest is the function returned by the callback function
  const handleRequest = (req, res) = > {
    const ctx = this.createContext(req, res);
    return this.handleRequest(ctx, fn);
  };
  return handleRequest;
}
Copy the code

The callback function does two things:

  1. The compose function is used to perform a layer check on the stack of cache middleware functions and return a function. The compose implementation will be explained in more detail later.

  2. Create a RequestListener to request the return of the listener function. If the client sends a request, the request listener function (handleRequest) is triggered to execute first, accepting the Request and response objects for each request.

Const CTX = this.createcontext (req, res) in the handleRequest function returns each request (req) and response (req) Res) objects are combined to create a context object (CTX for short) and create references between the three. Of course, this is not the focus of this article, but just briefly touched on.

We then hand the CTX and FN to The handleRequest for processing. Before going into the handleRequest, let’s see what the FN generated by compose does.

compose

Compose is a koa-compose NPM package with about 20 lines of core code that provides the core hosting for the middleware’s next function call. See the internal implementation. The following is the simplified code to clarify the operation logic.

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} ctx
   * @return {Promise}
   * @api public* /
  return function fn (ctx, next) {
    // Simplify some code
    return dispatch(0)
    function dispatch (i) {
      let middlewareFn = middleware[i]
      try {
        return Promise.resolve(middlewareFn(ctx, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}
Copy the code

The code is small, but the implementation is clever. **const fn = compose(this.middleware) ** the call to const fn = compose(this.middleware) ** checks each middleware function in the registered middleware stack and returns the FN function. So, just remember that the fn passed to the handleRequest function is like this:

return function fn (ctx, next) {
  // Simplify some code
  return dispatch(0)
  function dispatch (i) {
    let middlewareFn = middleware[i]
    try {
      return Promise.resolve(middlewareFn(ctx, dispatch.bind(null, i + 1)));
    } catch (err) {
      return Promise.reject(err)
    }
  }
}
Copy the code

handleRequest

Each time a client makes a request, it calls the RequestListener RequestListener function, creates a request response context object, and passes CTX and fn to the handleRequest function for processing. Therefore, each request requires the execution of middleware functions in the order in which the middleware is registered. Look at the internal implementation of the Koa handleRequest function:

handleRequest(ctx, fn) {
  // omit irrelevant code...
  const onerror = err= > ctx.onerror(err);
  const handleResponse = () = > respond(ctx);
  // omit irrelevant code...
  return fn(ctx).then(handleResponse).catch(onerror);
}
Copy the code

The context object (CTX) is passed into the FN function as a parameter, and after all the internal middleware execution is completed, the resolve is called to inform the external handleResponse function to process the subsequent response data. The first internal call to Dispatch (0) from FN pulls out the first middleware function, middlewareFn, in the middleware stack according to the custom subscript I. The first middleware function registered in the demo:

async (ctx, next) => {
  console.log(1);
  await next();
  console.log(2);
}
Copy the code


Execute the first middleware function to ** the context object (CTX)andDispatch. Bind (null, I +1) ** is passed as an argument to the middleware function. First execute console.log(1) to print 1, then executeawait next()Transfer execution of current middleware functions to Next (i.e.dispatch(1)), the second middleware function is taken out to execute, and so on until all middleware is executed.



After all the middleware functions are executed in turn, the execution right of the last middleware function is transferred to the previous middleware function after the last middleware function is removed from the stackawaitOut, continue the execution of the code.



Combined with demo code, through a piece of middleware function on the stack – out of the stack diagram, to a profound experience.





When Dispatch (0) is out of the stack, it means that all middleware functions are executed successively. If any error occurs in the execution process of a certain middleware, reject is thrown, which is processed by the external onError error handler. If there are no errors, the handleResponse function is called to respond to handle the body data format.

conclusion

  • The next of the middleware function is not the next middleware function called directly, but rather the dispatch function inside the fn called, which calls the next middleware function and passes the context object (CTX) and itself (Dispatch).

  • The order of execution of middleware functions is the same as the order of registration. Middleware functions that are used first are executed first.

  • When a middleware function completes execution, the corresponding dispatch function transfers execution to the **await next() of the previous middleware function, and ** executes the subsequent code of the middleware function await.