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

1,681 阅读3分钟

前言

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

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

总共分为六篇

node打怪升级系列 - 基础篇

node打怪升级系列 - Koa篇

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

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

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

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

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

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

image.png

正文开始

洋葱效果展示

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)

image.png

分析实现思路

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

use(fn) {
    // ...
    this.middleware.push(fn);
    return this;
  }

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

 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)

  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, 对中间件进行编排(核心:洋葱模型

image.png

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))为例

实际上,等于执行了


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

中的

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

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

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

 handleRequest(ctx, fnMiddleware) {
    // ...
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

完结

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

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

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

image.png