前言

天冷了,唯有学习来温暖自己。

最近利用业余的时间,跟着 coderwhy 老师学习 node.js,了解以及掌握一些服务端的常见知识:

确实学习到了很多东西,填充了自己的知识体系的未知领域。

node.js 也许是前端开发者踏入服务端开发的最好选择。同样的 JavaScript,同样的语法,以及同样的你,基本上可以达到无缝衔接的开发。

对于 node.js 而言,社区里面出现了非常多的框架,快速上手,敏捷开发。

koaexpress 就是其中的比较两个突出的框架。

在阅读下文之前,希望你掌握了 express 和 koa 的基本使用,不然下面内容对你的帮助也许不是那么的大。

koa 和 express 的介绍

express 是一个基于 node.js 平台,快速,开放,极简的 web 开发框架。express 官网

koa 是基于 node.js 平台的下一代 web 开发框架。koa 官网

两个框架都是同一个团队开发的,都是 node.js 中比较优秀的框架。

都是 node.js 框架,同一个团队为什么要开发两个呢?

express 也许是 node.js 最早出的框架,里面集成了大量的中间件,造成了框架的笨重性。团队领队人 TJ 发现了 express 的设计是有缺陷的,如果想要修复这个设计缺陷,就会造成 express 的框架重构。

基于上面的种种原因(当然还有不知道的),就打算重新写一个新的框架->koa,来实现 express 的不足之处。

express 现在有团队中的团员维护,基本上也很少更新了。

koa 由团队领队人 TJ 主要维护。

​ --来自 coderwhy 老师的闲谈

上面的闲谈的内容不重要,当个乐子开心一下就好了。

一起看看 express 和 koa 在 github 上的星标:
koa 和 express 的对比
可以发现 express 的使用率还是比 koa 高,尽管 express 笨重,设计有缺陷,但是对于开发者而言,当不考虑这些因素情况下,express 还是吃香的。

两者都是同一团队写的两个框架,那么核心的思想肯定是相同的,当然肯定会也存在差异,那么接下来就来重点比较一下其中的差异吧。

koa 和 express 的差异对比

下面主要从两个方面来分析其中的差异:设计架构中间件

因为也是第一次接触 koa (express 以前是接触了的),如果存在有误的地方,请指出来,虚心受教,共同进步。

koa 和 express 的设计架构对比

express 是完整和强大的,里面集成了大量的中间件,比如说:路由,静态资源等中间件。对于开发者而言,技术统一,都是使用 express 内部提供的。
koa 和 express 的对比
koa 是灵活和自由的,基本上没有集成任何中间件(核心代码只有大致1600行左右),中间件都需要自己去安装。对于开发者而言,选择技术的类型是多样的,丰富的。
koa 和 express 的对比

webstorm 和 vscode 的使用,都是因人而异,没有谁强谁弱。那么对于 express 和 koa 也是同样的道理,各自有各自优势。

接下来我们一起来听听 express 和 koa 独白:

express:hi,koa,我俩同处一体,我俩比比?

koa:???

express:我俩的发动机都是中间件,我有三个兄弟:requestresponsenext,你有几个兄弟呢?

koa:额,我有两个兄弟:contextnext。不过我这 context 兄弟很厉害,以一敌二(request,response)。

express:我自带路由(Router),直接使用即可,你呢?

koa:安装。(koa-router 或者 @dva/router

express:我可以自己暴露静态资源,你呢?

koa:安装。(koa-static

express:我只需要配置一下,就可以解析客户端传递 application/json 的格式数据,你呢?

koa:还是安装。(koa-bodyparser

express:你能不能不要安装呀,你就没有自带的?我还是只需要配置一下,就可以解析客户端传递 x-www-form-urlencoded 的格式数据,你呢?

koa:哈哈哈,不好意思,我不用配置,我也能解析 x-www-form-urlencoded 的格式数据。

koa:我还能安装 @koa/multer 来实现文件上传,你自带了吗?

express:额。。。我这个还真没自带,我也需要安装 multer 来实现。

koa:让你装 xxx。你我本是同根生,相煎何太急,和平相处不行嘛。

express:。。。

TJ:莫吵了,把你俩创建出来,设计理念本就是不相同的。express 你走的完整路线,koa 走的是灵活路线,所以不用相互较劲,和气生财。

koa 和 express 的中间件对比

express 和 koa 的核心,都是中间件。

简单的来说,理解了中间件,也就理解了express 和 koa。

何为中间件?

那么何为中间件呢?

express 和 koa 都能创建一个服务器实例 app,那么给 app 传入一个回调函数,那么该回调函数就被称为中间件(middleware)

express 创建中间件:

// 方式一:use(fn)
app.use((req, res, next) => {})
// 方式二:use(path, fn)
app.use('/', (req, res, next) => {})
// 方式三:method(path, fn)
app.get('/', (req, res, next) => {})
// 方式四:method(path, fn1, fn2, fn3)
app.get('/', (req, res, next) => {}, (req, res, next) => {})

koa 创建中间件:

// 方式一:use(fn)
app.use((ctx, next) => {})
// 方式二:use(path, fn)
app.use('/', (ctx, next) => {})

在这里就不展开怎么使用中间件了,我相信你会的,express 和 koa 的中间件道理是相同的。

中间件的执行顺序(同步,异步)

app.use((req, res, next) => {
  console.log("中间件01: next前");
  next();
  console.log("中间件01: next后");
});
app.use((req, res, next) => {
  console.log("中间件02: next前");
  next();
  console.log("中间件02: next后");
});
app.use((req, res, next) => {
  console.log("中间件03")
});

koa 和 express 的对比

function mockAsync () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(321)
    }, 1000)
  })
}
app.use((req, res, next) => {
  console.log("中间件01: next前");
  next();
  console.log("中间件01: next后");
});
app.use((req, res, next) => {
  console.log("中间件02: next前");
  next();
  console.log("中间件02: next后");
});
app.use(async (req, res, next) => {
  const data = await mockAsync()
  console.log("中间件03: next前");
});

koa 和 express 的对比

app.use((ctx, next) => {
  console.log('中间件01: next前');
  next()
  console.log('中间件01: next后');
})
app.use((ctx, next) => {
  console.log("中间件02: next前");
  next();
  console.log("中间件02: next后");
});
app.use((ctx, next) => {
  console.log("中间件03");
});

koa 和 express 的对比

function mockAsync() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(321);
    }, 1000);
  });
}
app.use((ctx, next) => {
  console.log('中间件01: next前');
  next()
  console.log('中间件01: next后');
})
app.use((ctx, next) => {
  console.log("中间件02: next前");
  next();
  console.log("中间件02: next后");
});
app.use(async (ctx, next) => {
  const res = await mockAsync()
  console.log("中间件03");
});

koa 和 express 的对比

上面四个案例,分别从:

来分析了中间的执行顺序,可以得出两点结论:

  1. 无论是 express 还是 koa,当中间件是同步代码并且调用了 next 函数,那么程序运行就会先执行每个中间件next函数之前的代码,当执行到最后一个中间件时,又会回滚执行每个中间件next 函数后的代码(类似于 数据结构中的 first in last out)。
  2. 无论是 express 还是 koa,当遇到中间件中存在异步代码,就会停止向下执行,而是回到上一个中间件继续执行。

所以对于 express 和 koa 在中间件执行上,**表现形式**上是相同的。

而不相同之处就是在于 express 和 koa 在针对中间件存在异步代码时,**处理方式**不同(简单的来说也就是内部的 next 函数实现不同)。

koa 和 express 不同的异步处理方式

假如存在这样的一个案例:存在两个中间件,一个是业务接口处理的中间件,一个是针对处理相同数据的中间件(比如:针对每个接口返回用户信息),这里的获取用户信息,就是异步操作。

那么针对 express 会写出以下代码:

function getUserInfo () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({name: 'copyer', sex: 'man'})
    }, 1000)
  })
}
app.get('/list', (req, res, next) => {
  const list = [
    { id: "1", content: "列表1" },
    { id: "2", content: "列表2" },
  ];
  next();
  res.json({
    list,
    userInfo: req?.userInfo // 返回用户信息,需要通过下个中间件来获取
  })
});
app.use(async (req, res, next) => {
  // mock 异步代码,拿到用户信息
  const data = await getUserInfo();
  console.log(data); // { name: 'copyer', sex: 'man' }
  req.userInfo = data; // 设置用户信息
});

当我们访问 list 接口时,发现是拿不到用户信息(userInfo),因为遇到是异步函数,就会停止继续执行下面的代码,而是回到上一个中间件继续执行,所以没有拿到用户信息。

koa 也是同样的道理,但是 koa 却是可以解决这个问题。代码如下:

function getUserInfo() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ name: "copyer", sex: "man" });
    }, 1000);
  });
}
router.get('/list', async (ctx, next) => {
  const list = [
    { id: "1", content: "列表1" },
    { id: "2", content: "列表2" },
  ];
  await next(); /*****************重点代码*********************/
  ctx.body = {
    list,
    ctx: ctx?.userInfo
  }
});
router.use(async (ctx, next) => {
  const res = await getUserInfo();
  console.log(res); // { name: 'copyer', sex: 'man' }
  ctx.userInfo = res; // 设置用户信息
});
app.use(router.routes())

看上面标记的重点代码,那么就是处理异步的关键。在 next 执行前面,加上一个 await,这样就能正常的拿到用户信息。

await next() 的意思就是等待下个中间件执行完成,那么这样用户信息也就设置成功了。

那么肯定有人这样想,那么我在 express 调用 next 函数时,也加上 await ,是不是也解决了?想法很美好,但是行不通的。为撒?

express 中的 next 函数,返回值一个void,没有返回值。

这里的 next 函数接受一个参数 err,也就是错误信息,针对全局异常处理的收集时,就会使用。

koa 和 express 的对比

koa 中的 next 函数,返回值一个Promise。

这里的 next 函数不接受参数,所以全局错误的异常处理,需要另想它法。

koa 和 express 的对比

koa 和 express 在异步处理函数,最大的差别就是 next 函数实现不同,那么也就造成了中间件在异步上的使用不同。

koa 在上一个中间件拿取下一个异步中间件的数据,然后返回。express 却是不行,这是 express 设计上的缺陷。

洋葱模型

想想平时生活中的洋葱,是不是一层一层的。

中间件从上往下(从外往里),然后从下往上(从里往外)执行,无论是同步还是异步都满足,koa 是符合洋葱模型的。

express 是在同步的时候是满足洋葱模型的,但是异步的时候却是不能满足洋葱模型。

总结

这篇主要从设计架构中间件两个方面来解释说明 express 和 koa 之间的差异。

比较差异并不是为了证明谁好谁弱,而是为了另一方面来充分认识框架隐藏的知识点,加深自己理解。

若你觉得有误,请指出;若对你有用,请点个赞。

发表回复