koa
koajs: next generation web framework for node.js.
Koa is the next generation web development framework based on Node.js. It is a simple and easy-to-use web framework based on middleware mechanism, which is elegant, concise, expressive and free. Web applications with specific functions are realized by freely combining various middleware with independent functions. Such as koa router middleware to achieve routing function, KOA CORS to achieve cross domain function and so on. Among these middleware, there is a special koa compose middleware. It is the use of this middleware that makes the onion model we are familiar with in KOA.
Onion model
Onion model is the serial control flow of koa middleware.
const Koa = require('koa'); const app = new Koa(); app.use(async (ctx, next) => { console.log('enter first middleware'); next(); console.log('out first middleware'); }); app.use(async (ctx, next) => { console.log('enter second middleware'); next(); console.log('out second middleware'); }); app.use(async (ctx, next) => { console.log('enter third middleware'); next(); console.log('out third middleware'); }); app.listen(3000);
The output is as follows:
How to make middleware run in the way of onion model is the function of KOA compose.
Koa compose source code
As shown in the above code, the middleware input parameters of use: ctx and next, where ctx is the context object in koa, what is next? With this problem, let's read the source code of KOA compose.
'use strict' /** * Expose compositor. */ module.exports = compose /** * 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 (typeof fn !== '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) } } } }
Koa compose input parameter is a middleware array, and return is an anonymous middleware function.
function compose (middleware) { // Type check input parameter must be an array if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') // Middleware function must be in the middleware array for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } // Return an anonymous middleware function return funxtion (ctx, next) {} }
Let's see what the anonymous function of return is:
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) } } }
Remove the judgment conditions and see what is the most specific
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
This also shows us what the next function is, dispatch.bind(null, i + 1). It is through dispatch that control is transferred to the next middleware.