在实现 compose 前,我们先来了解一下什么洋葱模型。
洋葱模型
洋葱模型,它就是 Koa 中间件的一种串行机制,并且是支持异步的。Koa中间件机制就是函数式组合概念 Compose的概念,将一组需要顺序执行的函数复合为一个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制。
下面是官方的一张图,即著名的洋葱圈模型:
我们再来看一下中间件的执行顺序:
下面是一个表达 “洋葱模型” 的经典案例:
const Koa = require("koa");const app = new Koa();app.use(asycn (ctx, next) => {console.log(1);await next();console.log(2);});app.use(asycn (ctx, next) => {console.log(3);await next();console.log(4);});app.use(asycn (ctx, next) => {console.log(5);await next();console.log(6);});app.listen(3000);
根据中间件的执行顺序,上面代码输出的结果为:1 => 3 => 5 => 6 => 4 => 2
compose 实现
洋葱圈的核心实现是 compose 函数,我们来盘点一下 compose 的各种实现。
Koa 中 compose 的实现方式
koa 递归实现
module.exports.compose = (middlewares = []) => {// 保证 middlewares 为数组if (!Array.isArray(middlewares)) {middlewares = Array.from(arguments);}// 保证 middlewares 的每一项为函数if (middlewares.some(fn => typeof fn !== 'function')) {throw new TypeError('Middleware must be composed of functions!');}return function () {// 取出第一个中间件函数执行return dispatch(0);// 递归函数function dispatch(i) {// 取出第 i 个中间件并执行let fn = middlewares[i];// 中间件函数不存在或者所有的中间件函数都执行完,返回成功态的 Promiseif (!fn) {return Promise.resolve();}// 执行后返回成功态的 Promisereturn Promise.resolve(fn(function next() { // 执行下一个中间件函数return dispatch(i + 1);}));}};};
Koa Reduce 实现
module.exports.compose = (middlewares = []) => () => {// 保证 middlewares 为数组if (!Array.isArray(middlewares)) {middlewares = Array.from(arguments);}// 保证 middlewares 内的每一项为函数if (middlewares.some(fn => typeof fn !== 'function')) {throw new TypeError('Middleware must be composed of functions!');}if (middlewares.length === 0) {// 没有中间件函数时返回 成功态的Promisereturn Promise.resolve();} else if (middlewares.length === 1) {// 只有一个中间件函数时,执行当前的中间件函数return Promise.resolve(middlewares[0].call(null, () => Promise.resolve()));} else {// compose reduce实现return middlewares.map(item => item).reverse().reduce((pre, cur) => () => cur(() => pre(() => {})))();}};
Koa Class 实现
class ComposeClass {constructor() {this.middlewares = [];this.index = 0;}middleware(middlewares) {this.middlewares = middlewares;return this.dispatch(this.index);}dispatch(index) {const fn = this.middlewares[index];if (!fn) {return Promise.resolve();}this.index += 1;return Promise.resolve(fn(this.next.bind(this)));}// 指定下一个中间件的执行next() {return this.dispatch(this.index);}}module.exports.compose = (middlewares = []) => {if (!Array.isArray(middlewares)) {middlewares = Array.from(arguments);}if (middlewares.some(fn => typeof fn !== 'function')) {throw new TypeError('Middleware must be composed of functions!');}return () => {return new ComposeClass().middleware(middlewares);};};
express 中的 compose 实现
express 递归实现
module.exports.compose = (middlewares = []) => {//if (!Array.isArray(middlewares)) {middlewares = Array.from(arguments);}if (middlewares.some(fn => typeof fn !== 'function')) {throw new TypeError('Middleware must be composed of functions!');}return async () => {let idx = 0;async function next() {if (idx === middlewares.length) {return Promise.resolve();}if (idx < middlewares.length) {return Promise.resolve(middlewares[idx++](next));}}return await next();};};
Redux 中的 compose 实现
Redux Reduce 实现
module.exports.compose = (middlewares = []) => {if (!Array.isArray(middlewares)) {middlewares = Array.from(arguments);}if (middlewares.length === 0) {return arg => arg;}if (middlewares.some(fn => typeof fn !== 'function')) {throw new TypeError('Middleware must be composed of functions!');}return (next = async () => {}) => middlewares.reduce((a, b) => arg => a(() => b(arg)))(next);};
Redux ReduceRight 实现
/*** @description: 利用reduceRight实现洋葱圈* @param {middlewares:中间件数组}* @return {高阶函数}*/module.exports.compose = (middlewares = []) => {if (!Array.isArray(middlewares)) {middlewares = Array.from(arguments);}if (middlewares.some(fn => typeof fn !== 'function')) {throw new TypeError('Middleware must be composed of functions!');}// reduceRight:从右向左做累加,第二个参数为初始值// 比如:middlewares = [f1, f2, f3],执行过程如下:// 第一次 --> a = () => {}, b = f3,结果:() => f3(() => {})// 第二次 --> a = () => f3(() => {}), b = f2,结果:() => f2(() => f3(() => {}))// 第三次 --> a = () => f2(() => f3(() => {})),b = f1,结果:() => f1(() => f2(() => f3(() => {})))return () =>middlewares.reduceRight((a, b) => () => b(a),() => {})();};
Redux ReduxRight Promise 实现
module.exports.compose = (middlewares = []) => {if (!Array.isArray(middlewares)) {middlewares = Array.from(arguments);}if (middlewares.some(fn => typeof fn !== 'function')) {throw new TypeError('Middleware must be composed of functions!');}return () =>Promise.resolve(middlewares.reduceRight((a, b) => () => Promise.resolve(b(a)),() => Promise.resolve())());};
责任链模式实现
class Handle {constructor(middleware) {this.middleware = middleware;this.next = null;}setNext(nextHandle) {this.next = nextHandle;}}module.exports.compose = (middlewares = []) => {if (!Array.isArray(middlewares)) {middlewares = Array.from(arguments);}if (middlewares.some(fn => typeof fn !== 'function')) {throw new TypeError('Middleware must be composed of functions!');}const handles = [];for (let i = 0; i < middlewares.length; i++) {const handle = new Handle(middlewares[i]);handles.push(handle);}for (let i = 0; i < handles.length; i++) {if (handles[i + 1]) {handles[i].setNext(handles[i + 1]);}}return function func() {return innerFunc(handles[0]);function innerFunc(currentHandle = handles[0]) {if (!currentHandle) {return Promise.resolve();}return Promise.resolve(currentHandle.middleware(() => innerFunc(currentHandle.next)));}};};
List 列表递归实现
const isArray = action => Array.isArray(action)?true:false;const isFunction = middlewares => middlewares.every(middleware => typeof middleware === 'function');module.exports.compose = (args) => async () => {let middlewares = [...args]if(!isArray(middlewares)) throw new TypeError('middlewares must be Array');if(!isFunction(middlewares)){throw new TypeError('middleware must be Function')}if(middlewares.length === 0){return null;}//生成链表//{next:{fn:b,next:{fn:next}}}function getStatck(statck){if(middlewares.length>0){let fn = middlewares.shift()statck.next = { fn }getStatck(statck.next)return statck;}}let statck = getStatck({});let filber = statck.next;return dispatch(filber)function dispatch(filber){if(!filber){return () =>{}}return filber.fn(next = async()=>{await dispatch(filber.next)})}}
