As we know, Koa middleware is implemented in a manner of Cascading code. Similar to the paper clip, please refer to the following picture:

This article examines how Koa’s middleware implements cascading execution. In KOA, to apply a middleware, we use app.use():

app
  .use(logger())
  .use(bodyParser())
  .use(helmet())
Copy the code

Use () ¶

  use(fn) {
    if (typeoffn ! = ='function') throw new TypeError('middleware must be a function! ');
    if (isGeneratorFunction(fn)) {
      deprecate('Support for generators will be removed in v3. ' +
                'See the documentation for examples of how to convert old middleware ' +
                'https://github.com/koajs/koa/blob/master/docs/migration.md');
      fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || The '-');
    this.middleware.push(fn);
    return this;
  }
Copy the code

Middlware this function adds the parameters from the call to use(fn) (either normal functions or middleware) to the this.middlware array.

IsGeneratorFunction (FN) is used to determine whether it is a Generator syntax, and convert(fn) is used. Convert to async/await syntax. All middleware is then added to this.Middleware and executed through callback(). Callback ()

  /** * Return a request handler callback * for node's native http server. * * @return {Function} * @api public */

  callback() {
    const fn = compose(this.middleware);

    if (!this.listeners('error').length) this.on('error'.this.onerror);

    const handleRequest = (req, res) = > {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }
Copy the code

In the source code, the compose() method transforms and cascdes the array of middleware we passed in, and the callback() returns the result of this.handlerequest (). Leaving out what is returned, let’s look at what the compose() method does to enable the incoming middleware to cascade and return a Promise.

Compose () is a library for KOA2 to implement middleware cascading calls, called koa-compose. Source code is very simple, only a function, as follows:

/** * 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) {
    // Record the location of the last middleware execution #
    let index = - 1
    return dispatch(0)
    function dispatch (i) {
      // In theory I should be greater than index because I is incremented each time I is executed.
      // If equal or less, next() is executed multiple times
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      // Get the current middleware
      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

You can see that compose() returns the result of an anonymous function that executes the dispatch() function itself, passing in 0 as an argument.

So what does dispatch(I) do? I is used as an argument to this function to get the middleware to the current subscript. A 0 is passed in at Dispatch (0) above to get the middleware[0] middleware.

The next() method is called multiple times, if true. Why is that? We’ll answer that question after we’ve explained all the logic.

Next, assign the current I to index, record the subscript of the currently executing middleware, and assign fn to get the middleware.

index = i;
let fn = middleware[i]
Copy the code

Once you get middleware, how do you use it?

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

The above code executes the middleware FN (Context, next) and passes the context and next functions. Context is the context object in KOA. The next function returns a dispatch(I +1) result. It is worth mentioning the I +1 parameter, which is passed as the next middleware execution, thus forming a recursive call. That’s why we need to do it manually when we write our own middleware, right

await next()
Copy the code

Only when the next function is executed can the next middleware be executed correctly.

As a result, each middleware can only execute Next once, and if you execute next more than once within a middleware, you will have problems. To get back to the previous question, why do I <=index tell you how many times next is executed?

Because normally index has to be less than or equal to I. Calling Next multiple times in one middleware will result in multiple dispatches (I +1). From the code point of view, each middleware has its own closure scope, the I of the same middleware is constant, and index is outside the closure scope.

When the first middleware, next() at Dispatch (0) is called, it should execute Dispatch (1), and when the following judgment is executed,

if (i <= index) return Promise.reject(new Error('next() called multiple times'))
Copy the code

If I <=index, the value of index= I is 1. If I <=index, I = 1. But if the first middleware executes one more next() internally, dispatch(2) is executed again. As mentioned above, the value of I in the same middleware is unchanged, so the value of I at this time is still 1, resulting in the situation of I <= index.

Some of you might wonder, right? If async itself returns promises, why use the promise.resolve () package layer? This is to be compatible with ordinary functions, so that ordinary functions can be used normally.

Let’s go back to the middleware execution mechanism to see what’s going on. We know that the async execution mechanism is to return a Promise only after all await asynthetics have been performed. So when we use async syntax to write middleware, the execution process is roughly as follows:

  1. Execute the first middleware first (becausecomposeWill be executed by defaultdispatch(0)), the middleware returnsPromiseAnd then beKoaListen, execute corresponding logic (success or failure)
  2. Encountered while executing the logic of the first middlewareawait next()“, the execution continuesdispatch(i+1), which is executiondispatch(1)Is manually triggered to execute the second middleware. At this point, the first middlewareawait next()The rest of the code is going to be pendingawait next()returnPromiseBefore continuing with the first middlewareawait next()The following code.
  3. The same is true when implementing the second middlewareawait next()The third middleware is manually executed,await next()The following code is still pendingawaitThe next middlewarePromise.resolve. Only after receiving the third middlewareresolveThe next code will be executed, and then the second middle will returnPromise, by the first middlewareawaitCapture, at which point the subsequent code of the first middleware is executed and then returnedPromise
  4. In this way, if there are multiple middleware, the above logic will continue to execute, first execute the first middleware, inawait next()Set pending to continue execution of the second middlewareawait next()Then the penultimate middleware executes the following code and returns a Promise, then the penultimate middleware executes the next code and returns a Promise, then the penultimate middleware executes the next code and returns a Promise, then the penultimate middleware executes the next code and returns a Promise, then the penultimate middleware executes in this manner until the first middleware executes and returnsPromise, so as to realize the sequence of execution of the graph at the beginning of the article.

After the above analysis, if you were to write a KOA2 middleware, the basic format would look like this:

async function koaMiddleware(ctx, next){
    try{
        // do something
        await next()
        // do something
    }
    .catch(err){
        // handle err}}Copy the code

I am currently writing a blog using KoA2 + React. If you are interested, go to the GitHub address: koa-blog-api