• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

koa系列koa洋葱模型和其compose原理

武飞扬头像
石小石Orz
帮助2

什么是洋葱模型

先来看一个 demo

  1.  
    const Koa = require('koa');
  2.  
    const app = new Koa();
  3.  
     
  4.  
    // 中间件1
  5.  
    app.use((ctx, next) => {
  6.  
    console.log("<<<1");
  7.  
    next();
  8.  
    console.log("1>>>");
  9.  
    });
  10.  
     
  11.  
    // 中间件 2
  12.  
    app.use((ctx, next) => {
  13.  
    console.log("<<<2");
  14.  
    next();
  15.  
    console.log("2>>>");
  16.  
    });
  17.  
     
  18.  
    // 中间件 3
  19.  
    app.use((ctx, next) => {
  20.  
    console.log("<<<3");
  21.  
    next();
  22.  
    console.log("3>>>");
  23.  
    });
  24.  
    app.listen(8000, '0.0.0.0', () => {
  25.  
    console.log(`Server is starting`);
  26.  
    });
学新通

输出的结果是:

  1.  
    <<<1
  2.  
    <<<2
  3.  
    <<<3
  4.  
    3>>>
  5.  
    2>>>
  6.  
    1>>>

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

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

深入 Koa 洋葱模型

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

  1.  
     
  2.  
    const Koa = require('koa');
  3.  
    const app = new Koa();
  4.  
     
  5.  
    // 中间件1
  6.  
    app.use((ctx, next) => {
  7.  
    console.log("<<<1");
  8.  
    next();
  9.  
    console.log("1>>>");
  10.  
    });
  11.  
     
  12.  
    // 中间件 2
  13.  
    app.use((ctx, next) => {
  14.  
    console.log("<<<2");
  15.  
    next();
  16.  
    console.log("2>>>");
  17.  
    });
  18.  
     
  19.  
    // 中间件 3
  20.  
    app.use((ctx, next) => {
  21.  
    console.log("<<<3");
  22.  
    next();
  23.  
    console.log("3>>>");
  24.  
    });
  25.  
    app.listen(8000, '0.0.0.0', () => {
  26.  
    console.log(`Server is starting`);
  27.  
    });
学新通

 use 方法

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

  1.  
    use(fn) {
  2.  
    this.middleware.push(fn);
  3.  
    }

 listen 方法

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

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

 callback 方法

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

一个简易版的callback方法

  1.  
    callback() {
  2.  
    //向 compose传递中间件函数数组
  3.  
    const fnMiddleWare = this.compose(this.middleware);
  4.  
     
  5.  
    //调用compose返回的方法,实现对所有中间件的调用
  6.  
    const handleRequest = (req, res) => {
  7.  
    fnMiddleWare()
  8.  
    .then(() => {
  9.  
    console.log("end");
  10.  
    res.end("my koa");
  11.  
    })
  12.  
    .catch((err) => {
  13.  
    res.end(err.message);
  14.  
    console.log("err", err);
  15.  
    });
  16.  
    };
  17.  
    return handleRequest;
  18.  
    }
学新通

 compose方法

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

  1.  
    compose(middleware) {
  2.  
    return function () {
  3.  
     
  4.  
    return xxxxx;
  5.  
    };
  6.  
    }

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

  1.  
    callback() {
  2.  
    const fnMiddleWare = this.compose(this.middleware);
  3.  
    fnMiddleWare()
  4.  
    .....
  5.  
    return xxxx;
  6.  
    }

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

  1.  
    compose(middleware) {
  2.  
    return function () {
  3.  
    const dispatch = (index) => {
  4.  
    ......
  5.  
    };
  6.  
    return dispatch(0);
  7.  
    };
  8.  
    }

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

  1.  
    // 异步递归遍历调用中间件处理函数
  2.  
    compose(middleware) {
  3.  
    return function () {
  4.  
    const dispatch = (index) => {
  5.  
    if (index >= middleware.length) return Promise.resolve();
  6.  
    const fn = middleware[index];
  7.  
    return Promise.resolve(
  8.  
    fn({}, () => dispatch(index 1)));
  9.  
    };
  10.  
    return dispatch(0);
  11.  
    };
  12.  
    }

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

dispatch( 0 )运行时

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

 我们逐步分析:

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

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

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

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

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

实际执行了这个匿名函数

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

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) 运行时

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

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

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

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

dispatch(2) 

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

dispatch(2) 运行时

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

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

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

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

dispatch(3) 

 dispatch(3) 运行时

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

其运行逻辑如下

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhiaechi
系列文章
更多 icon
同类精品
更多 icon
继续加载