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