聊聊Express和Koa这两个框架的异同,洋葱模型到底是什么东西?

1.1. 架构设计的异同

  • 在学习完 Express 和 Koa 之后,能感觉到语法上是没有太大区别的,那么为什么 TJ 还要再开发一个 Koa 呢?
  • 我们知道语法上是没有太大的区别的,并且都是以中间件 为核心,所以我们从架构设计上来说说它们的区别:
    • Express:
      • TJ最初在设计这个 Express 的时候,他的初衷就是将 Express 设计成一个完整且强大的框架,所以其中帮我们内置了很多功能
      • 比如什么:express.json 解析数据,router,部署静态资源的 express.static()等等,这些都已经被集成进来了,我们是不需要自己再去做的
    • Koa:
      • 但是Koa 就不一样了。因为我感觉现在整个前端的体系,它正在朝着越来越简洁的方向去发展
        • 意思就是每一个框架,都只做自己该做的事情,只保证自己的基本功能是ok的就行
        • 至于使用者为了开发起来更顺手或者更方便,想要使用其他的一些辅助库,那这个框架本身是并不关心的,所以也不会默认把它们集成进来
          • 比如Vue,在Vue2的时候,Vue本身还能被作为一个事件总线来使用,它有那个$emit。但是Vue3就摒弃这个东西了,我们要用的话,就自己封装或者自己去找一个库
      • 所以 Koa 由 Express 转过来之后,也是变得更加简洁和自由了
      • 它只包含最核心的功能,并不会对我们使用其他中间件进行任何的限制
        • 甚至是在app中连最基本的get、post 这种匹配 method 和 path 的功能都没有给我们提供
        • 我们需要自己来判断,或者通过路由来判断请求方式或者匹配路径
      • 所以说从语法上来说,它们两个可能很相似。但是从架构层面上来讲,我觉得它们两个是两个区别很大的框架
      • 它们两个各自追求的不一样,express追求完整和强大。但是koa追求的是简洁和自由
    • 所以总结一下就是,无论是什么框架,都是越发展越简洁,越轻量化了。对于一些库,我可能会给你推荐,但是并不会 "限制"!

1.2. 中间件执行机制的异同

  • 虽然 Express 和 Koa 框架他们的核心都是中间件:
  • 但是它们的中间件的执行机制是不同的,特别是针对某个中间件中==包含异步操作==时
  • 所以我们也可以从 Express 和 Koa中间件的执行顺序上来说一下它们的异同:

1.2.1. Koa 执行同步代码

  • 我们通过打断点可以发现,这三个中间件的执行顺序是:

    • 第一步:先指定第一个中间件,然后执行其 next 函数
    • 第二步:紧接着不会给客户端返回 ctx.msg,而是会继续执行第二个中间件
    • 第三步:会执行第二个中间件的 next 函数,然后执行第三个中间件
    • 第四步:第三个中间件执行完之后,紧接着又会回到第二个中间件中执行 next 函数后面的代码
    • 第五步:由于第二个中间件中的 next 函数后面没有代码了,所以就会执行第一个中间件中的 next 函数后面的 ctx.body = ctx.msg;
  • 所以我们会发现,Koa 在执行同步代码的时候,是由上到下,再由下到上的 ,就像剥洋葱 一样,由外到里,再由里到外

  • 所以无论是在第一个中间件中返回数据,还是在第三个中间件中给客户端返回数据

    • 返回的都是 aaabbbccc
    js 复制代码
    const Koa = require("koa");
    
    const app = new Koa();
    
    app.use((ctx, next) => {
      ctx.msg = "aaa";
      console.log("middleware_01");
      next();
    
      // 在第一个中间件中,返回数据
      ctx.body = ctx.msg;
    });
    
    app.use((ctx, next) => {
      ctx.msg += "bbb";
      console.log("middleware_02");
      next();
    });
    
    app.use((ctx, next) => {
      ctx.msg += "ccc";
      console.log("middleware_03");
    
      // 在第三个中间件中,返回数据
      // ctx.body = ctx.msg;
    });
    
    app.listen(8000, () => {
      console.log("服务器已开启");
    });

1.2.2. Koa 执行异步代码

  • 刚才说了,Koa 执行同步代码的时候,是由上到下,再由下到上的,并且在第三个中间件和第一个中间件中返回数据是一样的

  • 那么执行异步代码的时候,是什么样呢?

    • 我们将鼠标放到 next 函数上就会发现,next 函数的返回值是一个 Promise
    • 那么我们为了能够在第一个中间件中返回 ctx.msg,就必须得等到第二个和第三个中间件都执行完毕之后,才能执行第一个中间件后面的代码
    • 所以既然我们返回给客户端的数据,需要异步的获取,那么就必须得让这三个中间件都变成异步函数
    • 注意!!!
      • 即使是在第三个中间件中返回 ctx.msg,前两个中间件也都必须是异步函数,必须得用 await 等待下一个中间件执行完毕
      • 因为如果前面两个中间件不是异步函数的话,没有使用 await 的话,那么执行完第三个中间件之后
        • aixos 还在请求数据呢,但是第二个中间件已经执行 next 之后的后续代码了,
        • 第二个中间件的 next 后面什么都没有,那就执行第一个中间件
        • 第一个中间件 next 后面也什么都没有,就等于给客户端什么都没返回
      • 也就是说,如果执行的下一个中间件是一个异步函数,那么 next 函数是不会等下一个中间件的执行结果的,会直接进行下一步操作
      • 所以如果我们希望等待下一个中间件(异步函数)的执行结果,那么就需要给 next 前面加 await
    js 复制代码
    const Koa = require("koa");
    const axios = require("axios");
    
    const app = new Koa();
    
    app.use(async (ctx, next) => {
      ctx.msg = "aaa";
      console.log("middleware_01");
      await next();
    
      // 在第一个中间件中,返回数据
      ctx.body = ctx.msg;
    });
    
    app.use(async (ctx, next) => {
      ctx.msg += "bbb";
      console.log("middleware_02");
      await next();
    });
    
    // 将第三个中间件中的callback函数变成异步函数
    app.use(async (ctx, next) => {
      console.log("middleware_03");
    
      // 在第三个中间件中,通过异步操作获取数据
      const res = await axios.get("http://123.207.32.32:8000/home/multidata");
      ctx.msg += res.data.data.banner.list[0].title;
    
      // 在第三个中间件中,返回数据
      // ctx.body = ctx.msg;
    });
    
    app.listen(8000, () => {
      console.log("服务器已开启");
    });

1.2.3. Express 执行同步代码

  • Express 执行同步代码时,通过打断点可以发现,它和 Koa 执行同步代码的机制是一样的

  • 也是从上到下,再从下到上

    js 复制代码
    const express = require("express");
    
    const app = express();
    
    app.use((req, res, next) => {
      req.msg = "aaa";
      console.log("middleware_01");
    
      next();
    
      res.json(req.msg);
    });
    
    app.use((req, res, next) => {
      req.msg += "bbb";
      console.log("middleware_02");
    
      next();
    });
    
    app.use((req, res, next) => {
      req.msg += "ccc";
      console.log("middleware_03");
    });
    
    app.listen(8000, () => {
      console.log("服务器启动成功");
    });

1.2.4. Express 执行异步代码

  • Express 执行异步代码时,我们只能在进行异步操作的那个中间件(第三个中间件)中给客户端返回数据
    • 因为我们把鼠标放到 Express 中的 next 函数上会发现,它的返回值是 ==void==
    • 所以我们无法通过 async 和 await 来对 next 函数做限制,从而实现在第一个中间件中返回数据的这种方式
js 复制代码
const express = require("express");
const axios = require("axios");

const app = express();

app.use((req, res, next) => {
  req.msg = "aaa";
  console.log("middleware_01");

  next();
});

app.use((req, res, next) => {
  req.msg += "bbb";
  console.log("middleware_02");

  next();
});

app.use(async (req, res, next) => {
  console.log("middleware_03");
  // 在第三个中间件中,通过异步操作获取数据
  const resData = await axios.get("http://123.207.32.32:8000/home/multidata");
  req.msg += resData.data.data.banner.list[0].title;

  res.json(req.msg);
});

app.listen(8000, () => {
  console.log("服务器启动成功");
});

1.2.5. 总结

  • 也就是说,Koa 和 Express 在执行同步代码时(中间件中没有异步操作),它们两个是一样的

    • 都是由上到下,再由下到上
  • 但是,Koa 和 Express 在执行异步代码的时候,就完全不一样了

    • Koa 中 next 的返回值是一个 Promise,所以我们可以通过 async 和 await 来对其做限制 ,从而实现:
      • 从上到下时,我们能够完全控制,我们可以在最后一个中间件中做操作
      • 从下到上时,我们也能够完全控制,我们在最上面的中间件中,==可以==知道下面有异步操作的中间件在什么时候会执行完毕
        • 所以在下面的做异步操作的中间件中获取到的数据,将其存起来之后,最上面的中间件中是可以拿到的
    • 但是 Express 中的 next 的返回值是一个 void,所以我们无法通过 async 和 await 对其做限制 ,所以:
      • 从上到下时,我们能够完全控制,我们可以在最后一个中间件中做操作
      • 从下到上时,我们就无法控制了,我们在最上面的中间件中,==并不知道==下面有异步操作的中间件在什么时候会执行完毕
        • 所以在下面的做异步操作的中间件中获取到的数据,将其存起来之后,最上面的中间件中是拿不到的
  • 所以对于 Koa 和 Express 的这种特性,社区中有人将其非常形象的比作:剥洋葱模式

    • Koa 无论是执行同步代码还是异步代码,都符合剥洋葱模式的由外到里,再有里到外的规则
    • 但是 Express 只有在执行同步代码时,才符合剥洋葱模式的规则
      • 在执行异步代码时,是不符合剥洋葱模式的规则的
    • 洋葱模型其实就是Koa在执行中间件的时候的特性
相关推荐
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖9 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235249 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_7482402510 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar10 小时前
纯前端实现更新检测
开发语言·前端·javascript