【koa系列】koa洋葱模型及其compose原理解析

什么是洋葱模型

先来看一个 demo

const Koa = require('koa');
const app = new Koa();

// 中间件1
app.use((ctx, next) => {
    console.log("<<<1");
    next();
    console.log("1>>>");
});

// 中间件 2 
app.use((ctx, next) => {
    console.log("<<<2");
    next();
    console.log("2>>>");
});

// 中间件 3 
app.use((ctx, next) => {
    console.log("<<<3");
    next();
    console.log("3>>>");
});
app.listen(8000, '0.0.0.0', () => {
    console.log(`Server is starting`);
});

输出的结果是:

<<<1
<<<2
<<<3
3>>>
2>>>
1>>>

koa 中,中间件被 next() 方法分成了两部分。next() 方法上面部分会先执行,下面部门会在后续中间件执行全部结束之后再执行。可以通过下图直观看出:

 在洋葱模型中,每一层相当于一个中间件,用来处理特定的功能,比如错误处理、Session 处理等等。其处理顺序先是 next() 前请求(Request,从外层到内层)然后执行 next() 函数,最后是 next() 后响应(Response,从内层到外层),也就是说每一个中间件都有两次处理时机。    

深入 Koa 洋葱模型

我们以文章开始时候的 demo 来分析一下 koa 内部的实现。


const Koa = require('koa');
const app = new Koa();

// 中间件1
app.use((ctx, next) => {
    console.log("<<<1");
    next();
    console.log("1>>>");
});

// 中间件 2 
app.use((ctx, next) => {
    console.log("<<<2");
    next();
    console.log("2>>>");
});

// 中间件 3 
app.use((ctx, next) => {
    console.log("<<<3");
    next();
    console.log("3>>>");
});
app.listen(8000, '0.0.0.0', () => {
    console.log(`Server is starting`);
});

 use 方法

use 方法就是做了一件事,将用户自定义的函数模块收集到 middleware 中间件数组里

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

 listen 方法

执行 app.listen 方法的时候,其实是 Node.js 原生 http 模块 createServer 方法创建了一个服务,其回调为 callback 方法。

 listen(...args) {
    //请求进来后,我们在这里处理所有的中间件函数
    const server = http.createServer(this.callback());
    server.listen(...args);
  }

 callback 方法

callback方法就会将use方法存储的middleware中间件数组传给compose函数,callback方法方法内部借用了compose函数实现了对所有中间件函数的调用。

一个简易版的callback方法

 callback() {
    //向 compose传递中间件函数数组
    const fnMiddleWare = this.compose(this.middleware);

    //调用compose返回的方法,实现对所有中间件的调用
    const handleRequest = (req, res) => {
      fnMiddleWare()
        .then(() => {
          console.log("end");
          res.end("my koa");
        })
        .catch((err) => {
          res.end(err.message);
          console.log("err", err);
        });
    };
    return handleRequest;
  }

 compose方法

compose方法是洋葱模型的核心,其返回一个函数

compose(middleware) {
    return function () {
      
      return xxxxx;
    };
  }

 因此,在callback函数中,我们用变量进行了接收并调用

  callback() {
    const fnMiddleWare = this.compose(this.middleware);
    fnMiddleWare()
    .....
    return xxxx;
  }

 compose返回的函数体内,定义了一个dispath方法,我们在调用compose返回的函数时,实际在调用dispath这个方法

 compose(middleware) {
    return function () {
      const dispatch = (index) => {
         ......
      };
      return dispatch(0);
    };
  }

 现在,我们来看看 dispatch的主要逻辑

// 异步递归遍历调用中间件处理函数
  compose(middleware) {
    return function () {
      const dispatch = (index) => {
        if (index >= middleware.length) return Promise.resolve();
        const fn = middleware[index];
        return Promise.resolve(
          fn({}, () => dispatch(index + 1)));
      };
      return dispatch(0);
    };
  }

 当我们第一次执行fnMiddleWare()函数时,可以看出,实际执行的是dispatch(0)这个函数。

dispatch( 0 )运行时

​​​​​​​const dispatch = (index:0) => {
      if (index:0 >= middleware.length:3) return Promise.resolve();
      const fn = middleware[index:0];
      return Promise.resolve(
        fn({}, () => dispatch(index:0 + 1)));
    };

 我们逐步分析:

//如果index 大于 中件间函数的数量,返回一个成功状态的PROMISE
  if (index:0 >= middleware.length:3) return Promise.resolve();
const fn = middleware[index:0];  

 此时,fn为中间件的第一个回到函数

(ctx, next) => {
    console.log("<<<1");
    next();
    console.log("1>>>");
}
return Promise.resolve(
    fn({}, () => dispatch(index:0 + 1))
 );

最后一步,返回了一个Promise,因此,在callback函数内,我们需要使用。.then的方式来接收这个最终返回结果。我们中间件的所有执行逻辑,其实都在Promise.resolve里。

当调用 fn({ }, () => dispatch(index:0 + 1) )

实际执行了这个匿名函数

(ctx, next) => {
    console.log("<<<1");
    next();
    console.log("1>>>");
}

ctx此时的值是{ } ,这里的next的值是 () => dispatch(index:0 + 1) 匿名函数

 当这个匿名函数运行时,console.log("<<<1")正常执行,我们的浏览器会打印出

<<<1

当执行到这个next() 时,() => dispatch(index:0 + 1) 匿名函数执行,触发

dispatch(1) 

此时,第一个fn函数终止运行,开始执行 dispatch(1) ,因此console.log(" 1>>> ")暂时不执行

 dispatch(1) 运行时

const dispatch = (index:1) => {
      if (index:1 >= middleware.length:3) return Promise.resolve();
      const fn = middleware[index:1];
      return Promise.resolve(
        fn({}, () => dispatch(index:1 + 1)));
    };

 同理,此时会执行middleware中间函数数组的第二个匿名回调函数

(ctx, next) => {
    console.log("<<<2");
    next();
    console.log("2>>>");
}

 同样,先执行 console.log("<<<2") ,紧接着触发

dispatch(2) 

console.log("2>>>") 等待执行

dispatch(2) 运行时

 const dispatch = (index:2) => {
      if (index:2 >= middleware.length:3) return Promise.resolve();
      const fn = middleware[index:2];
      return Promise.resolve(
        fn({}, () => dispatch(index:2 + 1)));
    };

 同理,此时会执行middleware中间函数数组的第3个匿名回调函数

(ctx, next) => {
    console.log("<<<3");
    next();
    console.log("3>>>");
}

同样,先执行 console.log("<<<3") ,紧接着触发

dispatch(3) 

 dispatch(3) 运行时

const dispatch = (index:2) => {
    if (index:3 >= middleware.length:3) return Promise.resolve();
    const fn = middleware[index:2];
    return Promise.resolve(
      fn({}, () => dispatch(index:2 + 1)));
  };
  1. 返回Promise.resolve(),
  2. 相当于fn3的next( )函数执行完毕,开始执行 console.log("3>>>"),
  3. 此时fn2的next ( )函数执行完毕,开始执行fn2的 console.log("2>>>")
  4. 此时fn1的next ( )函数执行完毕,开始执行fn2的 console.log("1>>>")
  5. 至此,所有中间件函数执行完毕

其运行逻辑如下

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值