前端Middleware中间件基本原理

423 阅读3分钟

0.背景

中间件在前端的使用中越来越频繁,从最早提出中间件概念的express,再到后来的koa,再到前端框架Redux和库axios拦截器等,越来越多的库和框架用到了“中间件”的思想。

中间件模式特点:中间件模式中最妙的一处就是中间件的执行顺序

1.中间件的使用

const Koa = require('./like-koa2');
const app = new Koa();

// 中间件1
app.use(async (ctx, next) => {
  console.log("第一层洋葱--开始")
  await next();
  console.log("第一层洋葱--结束")
});

// 中间件2
app.use(async (ctx, next) => {
  console.log("第二层洋葱--开始")
  await next();
  console.log("第二层洋葱--结束")
});

// 中间件3
app.use(async ctx => {
  console.log("第三层洋葱--开始")
  ctx['body'] = 'Hello World';
  console.log("第三层洋葱--结束")
});

app.listen(3010);

上述使用的功能本质上就是如下流程

// 本质上的中间件运行流程如下
// 中间件1 执行
async function1(ctx, next) => {
  console.log("第一层洋葱--开始")
    // 执行中间件1的next1():执行fn2
    async function2(ctx, next) => {
      console.log("第二层洋葱--开始")
      // next2():执行fn3
      async ctx => {
          console.log("第三层洋葱--开始")
          ctx['body'] = 'Hello World';
          // next() 不管next有没有,执行没执行都是最后一个,dispatch(i+1)都超过了arr数组长度了
          console.log("第三层洋葱--结束")
      }
      console.log("第二层洋葱--结束") // 执行完之后函数弹栈,继续走刚才弹栈函数的外部函数(即上一个函数的流程)
    }
  console.log("第一层洋葱--结束")
}

2.实现原理

上面的流程具体是如何实现的呢? koa中间件模拟实现,其中compose为核心函数:而compose函数的核心是next函数实现:

next函数本质上为: dispatch.bind(null, i+1)

const http = require('http');

// 组合中间件
function compose(middlewaraeList) {

  return function (ctx) {
        return dispatch(0);
    
        function dispatch(i) {
          const fn = middlewaraeList[i]; // 1.按照注册顺序依次取出中间件队列中的函数
          try {
            // 防止fn不是Promise
            // 2.然后立即执行这个函数,use中的注册函数参数(context, next)实现
            // 2.1 context就是ctx传入
            // 2.2 next就是自身函数调用dispatch(i + 1):
            // 这样执行中间件函数1过程中执行next,就是相当于执行函数1的过程中立即再次调用diapatch(i+1),取出middlewaraeList[i+1]函数执行,当然需要dispatch.bind(null, i+1)
            return Promise.resolve(
              fn(ctx, dispatch.bind(null, i + 1)) // 实现next机制
            );
            // 那么有个问题:如果最后注册的那个中间件执行完毕之后呢?
            // 需要注意的是,最后注册的中间件不管有没有next执行,最后的注册函数都会走完,并且
            // 在他之前的所有中间件都会倒序往下走。可以把它理解为函数堆栈的过程。
          } catch (e) {
            return Promise.reject(e);
          }
        }
    
  }
}

class LikeKoa2 {
  constructor() {
    // 中间件存储的地方
    this.middlewaraes = [];
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }
  
  callback() {
    // 组合中间件
    const fn = compose(this.middlewaraes);
    return (req, res) => {
      // 创建上下文对象
      const ctx = this.createContext(req, res);
      // 将上下文传入中间件
      return this.handleRequest(ctx, fn);
    }
  }
  // 注册中间件,传入的fn函数就是注册传入的参数函数,这个参数函数自身还有两个参数:
  // 1.一个是context对象
  // 2.一个是next函数参数
  use(fn) {
    this.middlewaraes.push(fn);
    return this; // 链式调用
  }
 
  createContext(req, res) {
    const ctx = {
      req,
      res,
    };
    return ctx;
  }

  // 把上下文传入中间件函数
  handleRequest(ctx, fn) {
    return fn(ctx);
  }
  
}

module.exports = LikeKoa2;

axios中间件拦截器参考文献:juejin.cn/post/688275…

参考文献:toutiao.io/posts/afb3g…