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追求的是简洁和自由
- 但是Koa 就不一样了。因为我感觉现在整个前端的体系,它正在朝着越来越简洁的方向去发展
- 所以总结一下就是,无论是什么框架,都是越发展越简洁,越轻量化了。对于一些库,我可能会给你推荐,但是并不会 "限制"!
- Express:
1.2. 中间件执行机制的异同
- 虽然 Express 和 Koa 框架他们的核心都是中间件:
- 但是它们的中间件的执行机制是不同的,特别是针对某个中间件中==包含异步操作==时
- 所以我们也可以从 Express 和 Koa中间件的执行顺序上来说一下它们的异同:
1.2.1. Koa 执行同步代码
-
我们通过打断点可以发现,这三个中间件的执行顺序是:
- 第一步:先指定第一个中间件,然后执行其 next 函数
- 第二步:紧接着不会给客户端返回
ctx.msg
,而是会继续执行第二个中间件 - 第三步:会执行第二个中间件的 next 函数,然后执行第三个中间件
- 第四步:第三个中间件执行完之后,紧接着又会回到第二个中间件中执行 next 函数后面的代码
- 第五步:由于第二个中间件中的 next 函数后面没有代码了,所以就会执行第一个中间件中的 next 函数后面的
ctx.body = ctx.msg;
-
所以我们会发现,Koa 在执行同步代码的时候,是由上到下,再由下到上的 ,就像剥洋葱 一样,由外到里,再由里到外
-
所以无论是在第一个中间件中返回数据,还是在第三个中间件中给客户端返回数据
- 返回的都是
aaabbbccc
jsconst 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
- 即使是在第三个中间件中返回
jsconst 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 执行同步代码的机制是一样的
-
也是从上到下,再从下到上
jsconst 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 中 next 的返回值是一个 Promise,所以我们可以通过 async 和 await 来对其做限制 ,从而实现:
-
所以对于 Koa 和 Express 的这种特性,社区中有人将其非常形象的比作:剥洋葱模式
- Koa 无论是执行同步代码还是异步代码,都符合剥洋葱模式的由外到里,再有里到外的规则
- 但是 Express 只有在执行同步代码时,才符合剥洋葱模式的规则
- 在执行异步代码时,是不符合剥洋葱模式的规则的
- 洋葱模型其实就是Koa在执行中间件的时候的特性