⚡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);
  }

完结

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

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

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

相关推荐
程序员拂雨1 小时前
Node.js中的URL模块
node.js
BillKu1 小时前
Vue3 Element Plus 对话框加载实现
javascript·vue.js·elementui
郝YH是人间理想2 小时前
系统架构设计师案例分析题——web篇
前端·软件工程
Evaporator Core2 小时前
深入探索:Core Web Vitals 进阶优化与新兴指标
前端·windows
初遇你时动了情2 小时前
html js 原生实现web组件、web公共组件、template模版插槽
前端·javascript·html
QQ2740287563 小时前
Soundness Gitpod 部署教程
linux·运维·服务器·前端·chrome·web3
前端小崔3 小时前
从零开始学习three.js(18):一文详解three.js中的着色器Shader
前端·javascript·学习·3d·webgl·数据可视化·着色器
哎呦你好3 小时前
HTML 表格与div深度解析区别及常见误区
前端·html
运维@小兵3 小时前
vue配置子路由,实现点击左侧菜单,内容区域显示不同的内容
前端·javascript·vue.js
koiy.cc4 小时前
记录:echarts实现tooltip的某个数据常显和恢复
前端·echarts