⚡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) -> middleware[0] -> fn1(context, dispatch(1)) -> 打印 console.log("querying start 1");

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

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

  • 第四次执行 dispatch(3) -> middleware[3] -> 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);
  }

完结

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

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

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

相关推荐
zenRRan13 分钟前
Karpathy公开附议:AI Agent 的输出格式,正在从 Markdown 走向 HTML
前端·html
燐妤21 分钟前
前端HTML编程5:JavaScript完全指南
前端·javascript·html
八月欢喜25 分钟前
【Facebook】 实时消息监控难点解析
javascript·python·facebook
3D探路人29 分钟前
模灵 大模型聚合API 转发流程技术实现
java·大数据·开发语言·前端·人工智能·计算机视觉
烛阴1 小时前
Unity资源加载进化论:从AssetBundle到Addressables,一文带你吃透手游资源管理
前端·c#·unity3d
TO_WebNow1 小时前
使用thinkPHP8.x 访问接口提示跨域
前端·php
掘金一周1 小时前
回家的时候用车,不回家感觉又没啥用,这车还要不要买 | 沸点周刊 5.14
前端
zithern_juejin1 小时前
Map/Set/WeakMap/WeakSet
javascript
梦想的颜色1 小时前
前端UI宝藏SKILL——UI/UX Pro Max
前端·ui·ux
無名路人1 小时前
uniApp 小程序 vue3 app.vue静默登录其他页面等待登录完成方式二
前端·微信小程序·ai编程