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

完结

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

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

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

相关推荐
Jiaberrr14 分钟前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy38 分钟前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白38 分钟前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、39 分钟前
Web Worker 简单使用
前端
web_learning_32142 分钟前
信息收集常用指令
前端·搜索引擎
Ylucius1 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
tabzzz1 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
200不是二百1 小时前
Vuex详解
前端·javascript·vue.js
滔滔不绝tao1 小时前
自动化测试常用函数
前端·css·html5
LvManBa1 小时前
Vue学习记录之三(ref全家桶)
javascript·vue.js·学习