Koa middleware execution order

Recently in learning the principle of KOA middleware execution, look at the following code:

import Koa from 'koa';
import fetch from 'node-fetch';

const app = new Koa();

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

app.listen('3000'.() = > fetch('HTTP: 127.0.0.1:3000'))
Copy the code

Guess the output? Is it one, one, two, two, three, three? As a result,

1
2
3
3
2
1
Copy the code

To understand this behavior, understand KOA’s Onion model, according to which callbacks to middleware registered with app.use are iterated.

So the above middleware callbacks are executed in this order:

At callback time, if next() is called, the next middleware callback is entered, and iteration is stopped if one of the middleware throws an error.

The iterative implementation of middleware in KOA relies on the koa-compose package, with very little source code.

Implementation principles of KOA middleware

koa-compose.js


/**
 * 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) {
       // Call next() multiple times;
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      // Start with the first middleware callback
      index = i
      // Retrieve the callback
      let fn = middleware[i]
      // Iterated to the last registered middleware, fn assigned to next
      if (i === middleware.length) fn = next
      // If next is not defined, end, return a Promise
      // If the next callback is defined, execute next once, since the compose function can be used separately
      // comopose([middleware])(context, () => console.log(6666))
      if(! fn)return Promise.resolve()
      try {
        Execute the middleware callback and pass Dispatch as the second argument to next
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
       // Any middleware error stops the iteration and returns a failed Promise
        return Promise.reject(err)
      }
    }
  }
}
Copy the code

The compose input parameter middleware is a function registered with app.use above.

middleware: Function[] = [function.function.function];
Copy the code

The main core of the above is this line, passing dispath as the second argument to async(CTX, next) {} and binding the argument I +1 via bind

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

Why dispatch. Bind (null, I + 1), it’s mostly neat, and it’s actually ok if you write it this way, it’s all a closure.

Promise.resolve(fn(context, () = > dispatch.call(null, i + 1)));
Copy the code

Debugging koa – compose

It’s hard to read just by looking at the code. The best way to tease out the order of execution is through breakpoints. Adding debugging commands

{
  "scripts": {
    "debug": "node ./test/koa.js"}}Copy the code

Then you can easily debug with breakpoints