node.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > node.js > Koa和Express区别

浅谈Koa和Express的区别

作者:MariaH

本文主要介绍了浅谈Koa和Express的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前置核心概念

  1. Express:线性瀑布式中间件模型,基于回调,无原生 async/await 支持;
  2. Koa2:洋葱圈中间件模型,基于 async/await + compose,原生支持异步;
  3. 同步:代码无定时器、IO、数据库、Promise;
  4. 异步:定时器、文件读写、数据库、网络请求、Promise、async 函数。

统一测试需求:
3 层中间件,要求:

一、同步场景:Express vs Koa(无异步操作)

1. Express 同步中间件

const express = require('express')
const app = express()

app.use((req, res, next) => {
  req.msg = 'a'
  console.log('Express 中间件1 前置')
  next()
  // next之后的代码,所有下游中间件执行完才执行
  console.log('Express 中间件1 后置:', req.msg)
})

app.use((req, res, next) => {
  req.msg += 'b'
  console.log('Express 中间件2 前置')
  next()
  console.log('Express 中间件2 后置')
})

app.use((req, res) => {
  req.msg += 'c'
  console.log('Express 中间件3 执行完毕')
  res.send(req.msg)
})

app.listen(3000)

执行顺序(同步无任何异步)

Express 中间件1 前置
Express 中间件2 前置
Express 中间件3 执行完毕
Express 中间件2 后置
Express 中间件1 后置: abc

同步下 Express 特点

  1. 同步代码下,执行流程和Koa洋葱模型表现一致
  2. next() 是同步跳转,会立刻执行下一个中间件;
  3. next() 后面代码会等所有下游中间件全部走完再回头执行;
  4. 同步场景二者行为几乎无差异。

2. Koa 同步中间件

const Koa = require('koa')
const app = new Koa()

app.use((ctx, next) => {
  ctx.msg = 'a'
  console.log('Koa 中间件1 前置')
  next()
  console.log('Koa 中间件1 后置:', ctx.msg)
})

app.use((ctx, next) => {
  ctx.msg += 'b'
  console.log('Koa 中间件2 前置')
  next()
  console.log('Koa 中间件2 后置')
})

app.use(ctx => {
  ctx.msg += 'c'
  console.log('Koa 中间件3 执行完毕')
  ctx.body = ctx.msg
})

app.listen(8000)

输出顺序和Express完全一致

Koa 中间件1 前置
Koa 中间件2 前置
Koa 中间件3 执行完毕
Koa 中间件2 后置
Koa 中间件1 后置: abc

同步场景小结

同步代码时,Express 和 Koa 表现完全相同
自上而下执行 next() 前代码,全部走完后,自下而上执行 next() 后代码。
差异只在异步场景爆发。

二、异步场景:Express(巨大缺陷)vs Koa(完美可控)

场景设定:中间件2中加入异步延时操作(模拟数据库/接口请求)

// 模拟异步IO
function sleep(time) {
  return new Promise(resolve => setTimeout(resolve, time))
}

1. Express 异步中间件(重大问题)

const express = require('express')
const app = express()

app.use((req, res, next) => {
  req.msg = 'a'
  console.log('Express 中间件1 前置')
  next()
  // 异步导致此处提前执行,拿不到完整abc
  console.log('Express 中间件1 后置:', req.msg)
})

app.use(async (req, res, next) => {
  req.msg += 'b'
  console.log('Express 中间件2 前置')
  // 异步延时2秒
  await sleep(2000)
  next()
  console.log('Express 中间件2 后置')
})

app.use((req, res) => {
  req.msg += 'c'
  console.log('Express 中间件3 执行完毕')
  res.send(req.msg)
})

app.listen(3000)

执行输出顺序(错误)

Express 中间件1 前置
Express 中间件2 前置
Express 中间件1 后置: a  // 重点:异步阻塞前直接回头执行后置!
// 等待2秒后
Express 中间件3 执行完毕
Express 中间件2 后置

问题分析

  1. Express 不识别 async/awaitnext() 调用后直接同步返回,不会等待异步代码完成;
  2. 中间件2内部 await sleep 阻塞时,事件循环让出,Express 直接回到中间件1执行 next() 后面的代码;
  3. 中间件1后置代码提前执行,req.msg 只有 a,拿不到最终拼接结果;
  4. 无法实现“等所有下游逻辑完成再执行后置”的需求。

Express 异步解决方案(丑陋)

必须手动在异步结束后调用 next(),强行嵌套回调,回调地狱:

app.use((req, res, next) => {
  req.msg += 'b'
  console.log('中间件2 前置')
  setTimeout(() => {
    req.msg += '延迟b'
    next() // 异步完成后再放行
  }, 2000)
})

缺点:多层异步会无限嵌套,流程不可读、难以维护。

2. Koa 异步中间件(原生支持,流程不乱)

const Koa = require('koa')
const app = new Koa()

function sleep(time) {
  return new Promise(resolve => setTimeout(resolve, time))
}

app.use(async (ctx, next) => {
  ctx.msg = 'a'
  console.log('Koa 中间件1 前置')
  await next() // 等待所有下游中间件全部执行完成才往下走
  console.log('Koa 中间件1 后置:', ctx.msg)
})

app.use(async (ctx, next) => {
  ctx.msg += 'b'
  console.log('Koa 中间件2 前置')
  await sleep(2000) // 异步阻塞,不会跳出当前中间件
  await next()
  console.log('Koa 中间件2 后置')
})

app.use(ctx => {
  ctx.msg += 'c'
  console.log('Koa 中间件3 执行完毕')
  ctx.body = ctx.msg
})

app.listen(8000)

正确输出顺序

Koa 中间件1 前置
Koa 中间件2 前置
// 等待2秒
Koa 中间件3 执行完毕
Koa 中间件2 后置
Koa 中间件1 后置: abc

Koa 异步核心原理

  1. Koa 使用 compose 组合中间件,所有中间件被包装成 Promise 调用链;
  2. await next() 会暂停当前中间件,完整等待下游所有中间件(含异步)全部执行完毕,才恢复当前中间件剩余代码;
  3. 无论中间件内部有多少层异步、定时器、数据库操作,洋葱模型执行顺序永远稳定不变;
  4. 无回调嵌套,线性代码书写复杂异步流程。

三、同步/异步场景完整异同对照表

1. 相同点

同步代码场景

基础能力一致

2. 不同点(分同步、异步区分)

维度ExpressKoa2
同步执行逻辑线性流转,next后代码后置执行洋葱流转,和Express同步表现一致
异步执行逻辑next() 同步返回,不会等待异步;异步阻塞会直接跳出当前中间件,后置代码提前执行,流程错乱await next() 阻塞等待全部下游异步完成,洋葱顺序永久稳定
异步语法支持原生不支持async/await,需回调嵌套,极易产生回调地狱原生基于Promise+async/await,无嵌套
中间件底层实现数组顺序遍历,线性瀑布模型compose递归调用,Promise链式洋葱模型
上下文对象req、res分离,全局挂载数据容易冲突统一ctx上下文,一次请求独立ctx,数据隔离
异步错误捕获异步内部抛出错误无法被全局错误捕获,必须手动try/catch传err给next(err)全局可捕获async中间件抛出的错误,统一error事件处理
多异步串联多层异步必须嵌套,可读性极差平铺书写,await顺序执行,逻辑清晰

四、异步错误处理对比(补充关键差异)

Express 异步错误捕获缺陷

app.use(async (req, res, next) => {
  // 异步抛出错误,Express 捕获不到,直接崩溃
  throw new Error('数据库查询失败')
  next()
})

解决方式:必须手动 try/catch + next(err)

app.use(async (req, res, next) => {
  try {
    await sleep(1000)
    throw new Error('异常')
  } catch (err) {
    next(err) // 手动传递错误
  }
})
// 错误中间件接收
app.use((err, req, res, next) => {
  res.status(500).send(err.message)
})

Koa 异步错误天然支持

app.use(async (ctx, next) => {
  await sleep(1000)
  throw new Error('数据库异常')
})
// 全局统一捕获,无需手动传递
app.on('error', (err, ctx) => {
  ctx.status = 500
  ctx.body = { msg: err.message }
})

所有 async 中间件抛出的异常都会被 Koa 内部 Promise 捕获,自动触发全局 error 事件。

五、总结核心结论

只写同步代码:Express 和 Koa 几乎没有区别,洋葱/瀑布执行效果一样;

项目存在大量异步(数据库、Redis、文件、接口) :二者出现本质鸿沟:

核心根源:
Express 中间件只是普通回调函数,无 Promise 封装;
Koa 通过 compose 将所有中间件包装成 Promise 调用链,让异步流程可控。

到此这篇关于浅谈Koa和Express的区别的文章就介绍到这了,更多相关Koa和Express区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文