Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

  • 前端教程

    • 团队规范

    • Project

    • JS

    • NodeJS

      • Koa 洋葱模型原理分析
        • [#](#_1-认识洋葱模型) 1. 认识洋葱模型
        • [#](#_2-原理) 2. 原理
          • [#](#_2-1-中间件管理) 2.1 中间件管理
          • [#](#_2-2-next实现) 2.2 next实现
        • [#](#_3-思考) 3. 思考
        • [#](#参考) 参考
      • Nginx反向代理
      • Connect源码解析
      • Express源码解析
      • Node模块源码分析
      • MongoDB指南
      • 常用Node API
      • 常用Node工具总结
      • Node Debug for VSCode
      • Redis简介
    • Vue

    • React

    • 效率工具

    • 读书笔记

  • 教程
  • 前端教程
  • NodeJS
wangmings
2022-07-19
目录

Koa 洋葱模型原理分析

# # Koa 洋葱模型原理分析

Koa 有两个核心知识点:一个是中间件ctx,一个就是洋葱模型。

中间件ctx利用js原型,很巧妙的把request和response对象封装在里面。详细的实现可看笔者实现的简版koa build-your-own-koa (opens new window) (opens new window)。

另外一个核心就是本文要分析的洋葱模型。

# # 1. 认识洋葱模型

    const Koa = require('koa');
    
    const app = new Koa();
    const PORT = 3000;
    
    // #1
    app.use(async (ctx, next)=>{
        console.log(1)
        await next();
        console.log(1)
    });
    // #2
    app.use(async (ctx, next) => {
        console.log(2)
        await next();
        console.log(2)
    })
    
    app.use(async (ctx, next) => {
        console.log(3)
    })
    
    app.listen(PORT);
    console.log(`http://localhost:${PORT}`);
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

打印顺序:

    1
    2
    3
    2
    1
    
1
2
3
4
5
6

当程序运行到await next()的时候就会暂停当前程序,进入下一个中间件,处理完之后才会回过头来继续处理。

# # 2. 原理

核心:中间件管理和next实现,其中next是巧妙的使用了Promise特性。洋葱模型,本质上是Promise.resolve()的递归。

# # 2.1 中间件管理

app.listen使用了this.callback()来生成node的httpServer的回调函数。

    listen(...args) {
        debug('listen');
        const server = http.createServer(this.callback());
        return server.listen(...args);
    }
    
1
2
3
4
5
6

Koa.js中间件引擎是有koa-compose模块,即如下的compose方法

    callback() {
        const fn = compose(this.middleware); // 核心:中间件的管理和next的实现
        
        if (!this.listeners('error').length) this.on('error', this.onerror);
        
        const handleRequest = (req, res) => {
          const ctx = this.createContext(req, res); // 创建ctx
          return this.handleRequest(ctx, fn);
        };
        
        return handleRequest;
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13

当我们app.use的时候,只是把方法存在了一个数组里

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

# # 2.2 next实现

dispatch函数,它将遍历整个middleware,然后将context和dispatch(i + 1)传给middleware中的方法。

dispatch return Promise这段代码就很巧妙的实现了两点:

  1. 将context一路传下去给中间件
  2. 将middleware中的下一个中间件fn作为未来next的返回值
    function compose (middleware) {
      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 {
              // 核心代码:返回Promise
              // next时,交给下一个dispatch(下一个中间件方法)
              // 同时,当前同步代码挂起,直到中间件全部完成后继续
            return Promise.resolve(fn(context, function next () {
              return dispatch(i + 1)
            }))
          } catch (err) {
            return Promise.reject(err)
          }
        }
      }
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# # 3. 思考

洋葱模型实现原理,等同于如下代码:

next()返回的是promise,需要使用await去等待promise的resolve值。

promise的嵌套就像是洋葱模型的形状就是一层包裹着一层,直到await到最里面一层的promise的resolve值返回。

    Promise.resolve(middleware1(context, async() => { // 注意async关键字不能省略
      return Promise.resolve(middleware2(context, async() => {
        return Promise.resolve(middleware3(context, async() => {
          return Promise.resolve();
        }));
      }));
    }))
    .then(() => {
        console.log('end');
    });
    
1
2
3
4
5
6
7
8
9
10
11

# # 参考

  • https://segmentfault.com/a/1190000013981513
  • https://segmentfault.com/a/1190000019897506
编辑 (opens new window)
从V8 sort源码看插入排序
Nginx反向代理

← 从V8 sort源码看插入排序 Nginx反向代理→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式