资讯专栏INFORMATION COLUMN

一步步去阅读koa源码,中间件执行原理

WelliJhon / 1247人阅读

摘要:设置了一个出事索引值调用函数,开始时候传进去声明函数,把传进来的赋值给拿出第个中间件函数,赋值给判断如果等于的长度就把赋值给如果是假的返回一个同时执行,也就是中间件,把函数传递到里面递归调用自己上面的代码是这个部分的精华。

koa的中间件执行的流程控制,代码的是非常精妙的。由下面的一张洋葱模型的图来形容,记住这张图。

为什么是这样子的图,下面我们有一个例子来描述一下

const Koa = require("koa")
const app = new Koa()
// fn1
app.use(async (ctx, next) => {
  console.log("fn1-1")
  next()
  console.log("fn1-2")
})

// fn2
app.use(async (ctx, next) => {
  console.log("fn2-1")
  next()
  console.log("fn2-2")
})

// fn3
app.use(async (ctx, next) => {
  console.log("fn3-1")
  next()
  console.log("fn3-2")
})

app.listen(4002)

// fn1-1、fn2-1、fn3-1、fn3-2、fn2-2、fn1-2

上面的这个例子,顺序打印出来的是fn1-1、fn2-1、fn3-1、fn3-2、fn2-2、fn1-2,现在只知道,调用next()函数就会把控制流程就跳到下一个中间件,知道执行所有完之后然后再逐步向上执行前一个next后面的代码。这根跟洋葱有很大的相像似性(如果你愿意一层一层一层的剥开我的心~~~)。

探索

但是其中的原理是什么呢??下面我们一步步去探索。

首先是调用 app.use(fn) 这行代码,这行代码在源码里面,删除一些代码判断,是这样子的

constructor() {
  super();
  this.middleware = [];
}

use(fn) {
  this.middleware.push(fn);
  return this;
}

就是把所有函数push到一个middleware的数组之中,这个use就是专门干这中勾当的。

好了知道use的作用了,执行了use之后 我们的middleware中就有很多中间件函数了,下面我们继续看下去。

然后执行到 app.listen函数之后,代码如下

listen(...args) {
  // 创建一个server
  const server = http.createServer(this.callback());
  return server.listen(...args);
}

我们看到里么有个this.callback()执行函数,然后我们跳到这个函数里面。

callback() {
  // 我们看这里
  const fn = compose(this.middleware);

  const handleRequest = (req, res) => {
    const ctx = this.createContext(req, res);
    // 这个节点我们请记住下面这一行代码
    return this.handleRequest(ctx, fn);
  };

  return handleRequest;
}

这个callback函数里面,执行了compose函数,并且把middleware数组作为参数传递进去。

执行到了compose函数,下面我们就看看compose里面有什么。

compose函数就是一开始引用了koa-compose模块,简化之后发现里面的代码如下,简化后就简简单单的20几行代码,后面会详细解释下面的代码。

function compose (middleware) {
  return function (context, next) {
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

执行这个compose返回一个函数,这也是最核心的一个函数。注意这是上面的callback调用的。得到一个fn函数

看上面的callback调用的

然后执行到this.handleRequest(ctx, fn); 这个函数吧ctxfn(这个就是上面compose返回的函数)作为参数,传入到this.handleRequest中。 代码如下。

handleRequest(ctx, fnMiddleware) {
  return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}

到这里才真正的执行了compose返回的函数,把ctx传进去。然后我们继续看这个函数fnMiddleware(ctx),其实就是下面这样子的。

function (context, next) {
  // 设置了一个出事索引值
  let index = -1
  // 调用dispatch函数,开始时候传0进去
  return dispatch(0) 

  // 声明dispatch函数,
  function dispatch (i) {
    // 把传进来的赋值给index
    index = i
    // 拿出middleware第i个中间件函数,赋值给fn
    let fn = middleware[i] 
    // 判断如果i 等于middleware的长度 就把next 赋值给 fn
    if (i === middleware.length) fn = next
    // 如果fn是假的 return return Promise.resolve()
    if (!fn) return Promise.resolve()
    try {
      // 返回一个Promise.resolve, 同时执行fn, 也就是中间件,把next 函数传递到fn里面 
      return Promise.resolve(fn(context, function next () {
        // 递归调用自己
        return dispatch(i + 1)
      }))
    } catch (err) {
      return Promise.reject(err)
    }
  }
}

上面的代码是这个部分的精华。这里详细的说一下,首先定义了一个indexdispatch函数, 然后一开始调用dispatch(0)函数,里面把0赋值给了index,然后从middleware的数组(例子中我们有三个中间件函数)中拿到第0个中间件函数,赋值给fn,经过两个if都不符合条件,然后执行

return Promise.resolve(fn(context, function next () {
    // 递归调用自己
    return dispatch(i + 1)
  }))

这里的执行fn 中间件函数,并且把ctx function next () { return dispatch(i + 1) }) 作为参数传递进去。这个时候代码如下一幕了然

app.use(async (ctx, next) => {
  console.log("fn1-1")
  next() // 执行传入的next
  console.log("fn1-2")
})

执行这个函数 就会打印出fn1-1 然后就会执行next()函数,看上上一块代码,执行next()函数里面会调用 dispatch(i + 1) 也就是调用第fn = middleware[1] 正是第二个中间件。

看到这里大家就大概明白了。然后进入第二个中间件执行fn,打印出fn2-1,继续执行next()函数,next函数里面继续调用 dispatch(i + 1)

也就是fn = middleware[2] 第三中间件函数,打印出fn3-1,继续执行next()函数里面会调用 dispatch(i + 1),也就是fn = middleware[3]

这里注意了,if (i === middleware.length) fn = next到这里会符合这个条件,然后把next 赋值给fn 这里的next就是这个fnMiddleware(ctx).then(handleResponse).catch(onerror);调用时候传入的,然而这里并没有传入,所以这时候 fn 就是 undefined,然后继续执行到if (!fn) return Promise.resolve() 返回一个空的值,这就是第三个中间件的next执行结果,

然后继续执行下一行就打印出了fn3-2,最后向上执行到fn2-2,然后到fn1-2, 整个中间件的执行过程。很像洋葱模型,一层层进入,然后一层层出来。

好了整个中间件执行过程就是酱紫啦~~~

最后安利一波博客: https://github.com/naihe138/n...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95475.html

相关文章

  • 步步阅读koa源码,整体架构分析

    摘要:阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。使用其实某个框架阅读源码的时候,首先我们要会去用这个框架,因为用了我们才知道,某个是怎么用,哪里有坑,哪里设计的精妙。 阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。大到架构设计,小到可取的命名风格,还有设计模式、实现某类功能使用到的数据结构和算法等等。 使用koa 其实某个框架阅读源码的时候,首...

    haoguo 评论0 收藏0
  • 步步阅读koa源码,整体架构分析

    摘要:阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。使用其实某个框架阅读源码的时候,首先我们要会去用这个框架,因为用了我们才知道,某个是怎么用,哪里有坑,哪里设计的精妙。 阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。大到架构设计,小到可取的命名风格,还有设计模式、实现某类功能使用到的数据结构和算法等等。 使用koa 其实某个框架阅读源码的时候,首...

    chaos_G 评论0 收藏0
  • 如何阅读源码--Koa为例

    摘要:最近一年零零散散看了不少开源项目的源码多少也有点心得这里想通过这篇文章总结一下这里以为例前段时间其实看过的源码但是发现理解的有点偏差所以重新过一遍不得不说阅读的代码真的收获很大没啥奇技淫巧代码优雅设计极好注释什么的就更不用说了总之还是推荐把 最近一年零零散散看了不少开源项目的源码, 多少也有点心得, 这里想通过这篇文章总结一下, 这里以Koa为例, 前段时间其实看过Koa的源码, 但是...

    zhoutk 评论0 收藏0

发表评论

0条评论

WelliJhon

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<