聊聊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在执行中间件的时候的特性
相关推荐
腾讯TNTWeb前端团队1 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试