koa中间件实现原理和源码解析

在实际使用中可以看出,koa中间件的执行是洋葱模型,请求进入时依次执行中间件,返回结果时再反过来依次执行中间件,如下图:

 

那么这种执行机制是如何实现的呢,接下来探讨一下该执行机制的实现原理:

首先看koa中间件执行源码:

  listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
  callback() {
    const fn = compose(this.middleware);

    if (!this.listenerCount('error')) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }
 handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

分析源码可以看出,koa在执行中间时把中间件数组传递给了compose函数,compose源码如下:

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)
      }
    }
  }
}

从源码可以看出,中间件数组在经过compose处理后,返回了一个接收context和next参数的函数。

1,这个函数返回的又是一个promise对象。

2,而es7中新增的async await函数会结合promise使用,实现函数执行权的传递和等待。

koa中间件实例:

async function middleware1(ctx, next) {
  console.log('action 001');
  ctx.data.push(1);
  await next();
  console.log('action 006');
  ctx.data.push(6);
}

中间件执行分析:

下面是application中和middleware相关的源码:

  handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

执行过程:

1,compose函数返回一个第一个中间件函数:(context, next) => Promise.resolve(fn1(context, dispatch.bind(null, i + 1)));

2,执行fnMiddleware (ctx).then()时会执行 fn1(context, dispatch.bind(null, i + 1))

3,从中间件的代码可以看到next参数当前是dispatch.bind(null, i + 1),而dispatch.bind(null,i+1)返回的是promise对象

     await next()实际上是等待下一个中间件执行完毕

4,依次执行中间件知道最后一个,这就是洋葱模型的进入阶段;

退出阶段:

5,执行完最后一个中间件,此时会执行倒数第二个中间件的剩余逻辑,即await next()之下的代码

6,依次向上执行,出洋葱圈模型。

关于koa中间件源码的简单理解:

从compose可以看出,dispatch函数实现了promise.resolve的嵌套,koa中间件的生命周期就是 Promise.resolve 的嵌套;

每个生命周期是:Promise.resolve(middleware)嵌套中执行中间件

Promise.resolve(middleware1)

  middleware1前置操作 等待Promise.resolve(middleware2)

    middleware2前置操作 等待Promise.resolve(middleware3)

      ...

    middleware3中后置操作

  middleware2中后置操作

middleware1中后置操作

用图表表示:

参考:https://github.com/koajs/koa

https://chenshenhai.github.io/koajs-design-note/note/chapter02/02.html

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值