⚡node系列 - 原来就你是洋葱模型

前言

NodeJS这东西是不是学过了,之后感觉又像没学到什么东西???

我最近翻到了之前学习node的笔记,又结合了一些大佬的经验,这里把node系列相关的东西串联一下,分享给小伙伴们,顺便我自己也加深一下印象。

总共分为六篇

node打怪升级系列 - 基础篇

node打怪升级系列 - Koa篇

node打怪升级系列 - 浅谈require函数

node打怪升级系列 - 手写中间件篇

node打怪升级系列 - 手写发布订阅和观察者篇

node打怪升级系列 - 手写compose(洋葱模型)

node打怪升级系列 - 手写脚手架交互式命令界面

本文重点记录下洋葱篇里的装备

正文开始

洋葱效果展示

js 复制代码
const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  console.log("querying start 1");
  next();
  console.log("querying end 1");
})


app.use(async (ctx, next) => {
  console.log("querying start 2");
  next();
  console.log("querying end 2");
})


app.use(async (ctx, next) => {
  console.log("querying start 3");
  next();
  console.log("querying end 3");
})

const main = ctx => {
  ctx.body = "hello world";
}

app.use(main)
app.listen(3008)

分析实现思路

1, app.use收集函数到中间件

js 复制代码
use(fn) {
    // ...
    this.middleware.push(fn);
    return this;
  }

2, app.listen创建服务,并对中间件进行编排处理

js 复制代码
 listen(...args) {
    // ...
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }

根据http.createServer(this.callback())可知,callback函数的返回值是一个带有形参(req,res)的函数

callback的作用

  • 对中间件进行编排:const fn = compose(this.middleware)

  • 把ctx传给fn来配合编排的逻辑:this.handleRequest(ctx, fn) 等于fn(ctx)

js 复制代码
  callback() {
    const fn = compose(this.middleware);
    // ...
    const handleRequest = (req, res) => {
     // 把req res等核心信息注入到ctx中
      const ctx = this.createContext(req, res);
      // ...
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }

2.1, 对中间件进行编排(核心:洋葱模型

js 复制代码
function compose(middleware) {
  // ...

  /**
   * @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)
      }
    }
  }
}

洋葱模型的核心在于dispatch()函数,是一个递归逻辑

洋葱效果展示区域的代码举例,使用了三次app.use()后,中间件数组middleware成员为[fn1, fn2, fn3]

  • 第一次执行 dispatch(0) -> middleware0 -> fn1(context, dispatch(1)) -> 打印 console.log("querying start 1");

  • 第二次执行 dispatch(1) -> middleware1 -> fn2(context, dispatch(2)) -> 打印 console.log("querying start 2");

  • 第三次执行 dispatch(2) -> middleware2 -> fn3(context, dispatch(3)) -> 打印 console.log("querying start 3");

  • 第四次执行 dispatch(3) -> middleware3 -> fn3没有 -> return Promise.resolve()

  • 接下来执行各个函数中之前未执行完成的代码

  • 打印console.log("querying end 3");

  • 打印console.log("querying end 2");

  • 打印console.log("querying end 1");

对了,顺便再多说一句 fn1(context, dispatch(1))为例

实际上,等于执行了

js 复制代码
app.use(async (ctx, next) => {
  console.log("querying start 1");
  next();
  console.log("querying end 1");
})

中的

js 复制代码
(ctx, next) => {
  console.log("querying start 1");
  next();
  console.log("querying end 1");
})

context 等于ctxdispatch(x) 等于 next()

2.2, 把ctx传给fn来配合编排的逻辑

js 复制代码
 handleRequest(ctx, fnMiddleware) {
    // ...
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

完结

这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。

欢迎转载,但请注明来源。

最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。

相关推荐
IT_陈寒1 小时前
Java线程池用错参数,我的服务居然悄悄崩溃了
前端·人工智能·后端
光影少年1 小时前
Express 中间件原理
后端·node.js·express
卡布鲁1 小时前
React useMemo 与 useCallback 完全指南:原理、场景与避坑
前端·react.js
碳基修炼1 小时前
排查记:本地 devServer 是 http,浏览器却把 302 重定向升级成了 https
前端·http
时速GEO系统1 小时前
初元 AI V1.1 版本升级,首个正式版发布一周・实现生成、部署、上线、优化全流程自动化闭环
人工智能·数据挖掘·node.js
步行cgn1 小时前
Spring p 命名空间注入详解
java·前端·spring
何何____1 小时前
Vue 生命周期详解
前端·javascript
江米小枣tonylua2 小时前
TypeScript 全面の拥抱 !Prisma 8 + Electron 升级实战
前端
闪耀之光M782 小时前
npm命令解析
前端
咸鱼老弟2 小时前
AI Agent 的"自主性悖论"——为什么给它的自由度越大,越要配一套更硬的护栏
前端·人工智能