淺談Koa和Express的區(qū)別
前置核心概念
- Express:線性瀑布式中間件模型,基于回調,無原生
async/await支持; - Koa2:洋蔥圈中間件模型,基于
async/await + compose,原生支持異步; - 同步:代碼無定時器、IO、數據庫、Promise;
- 異步:定時器、文件讀寫、數據庫、網絡請求、Promise、async 函數。
統一測試需求:
3 層中間件,要求:
- 中間件1:前置拼接
a,后置輸出最終結果 - 中間件2:拼接
b - 中間件3:拼接
c
最終在中間件1后置打印message = abc
一、同步場景: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之后的代碼,所有下游中間件執(zhí)行完才執(zhí)行
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 執(zhí)行完畢')
res.send(req.msg)
})
app.listen(3000)
執(zhí)行順序(同步無任何異步)
Express 中間件1 前置
Express 中間件2 前置
Express 中間件3 執(zhí)行完畢
Express 中間件2 后置
Express 中間件1 后置: abc
同步下 Express 特點
- 同步代碼下,執(zhí)行流程和Koa洋蔥模型表現一致;
next()是同步跳轉,會立刻執(zhí)行下一個中間件;next()后面代碼會等所有下游中間件全部走完再回頭執(zhí)行;- 同步場景二者行為幾乎無差異。
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 執(zhí)行完畢')
ctx.body = ctx.msg
})
app.listen(8000)
輸出順序和Express完全一致
Koa 中間件1 前置
Koa 中間件2 前置
Koa 中間件3 執(zhí)行完畢
Koa 中間件2 后置
Koa 中間件1 后置: abc
同步場景小結
同步代碼時,Express 和 Koa 表現完全相同:
自上而下執(zhí)行 next() 前代碼,全部走完后,自下而上執(zhí)行 next() 后代碼。
差異只在異步場景爆發(fā)。
二、異步場景: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()
// 異步導致此處提前執(zhí)行,拿不到完整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 執(zhí)行完畢')
res.send(req.msg)
})
app.listen(3000)
執(zhí)行輸出順序(錯誤)
Express 中間件1 前置
Express 中間件2 前置
Express 中間件1 后置: a // 重點:異步阻塞前直接回頭執(zhí)行后置!
// 等待2秒后
Express 中間件3 執(zhí)行完畢
Express 中間件2 后置
問題分析
- Express 不識別
async/await,next()調用后直接同步返回,不會等待異步代碼完成; - 中間件2內部
await sleep阻塞時,事件循環(huán)讓出,Express 直接回到中間件1執(zhí)行next()后面的代碼; - 中間件1后置代碼提前執(zhí)行,
req.msg只有a,拿不到最終拼接結果; - 無法實現“等所有下游邏輯完成再執(zhí)行后置”的需求。
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() // 等待所有下游中間件全部執(zhí)行完成才往下走
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 執(zhí)行完畢')
ctx.body = ctx.msg
})
app.listen(8000)
正確輸出順序
Koa 中間件1 前置
Koa 中間件2 前置
// 等待2秒
Koa 中間件3 執(zhí)行完畢
Koa 中間件2 后置
Koa 中間件1 后置: abc
Koa 異步核心原理
- Koa 使用
compose組合中間件,所有中間件被包裝成 Promise 調用鏈; await next()會暫停當前中間件,完整等待下游所有中間件(含異步)全部執(zhí)行完畢,才恢復當前中間件剩余代碼;- 無論中間件內部有多少層異步、定時器、數據庫操作,洋蔥模型執(zhí)行順序永遠穩(wěn)定不變;
- 無回調嵌套,線性代碼書寫復雜異步流程。
三、同步/異步場景完整異同對照表
1. 相同點
同步代碼場景
- 兩者執(zhí)行順序完全一致:自上而下前置 → 自下而上后置;
next()同步跳轉,下游全部執(zhí)行完再回頭執(zhí)行后置邏輯;- 簡單同步接口(無數據庫/文件)表現無區(qū)別。
基礎能力一致
- 都基于 Node http 模塊封裝;
- 都依靠中間件處理請求;
- 都支持多層中間件堆疊。
2. 不同點(分同步、異步區(qū)分)
| 維度 | Express | Koa2 |
|---|---|---|
| 同步執(zhí)行邏輯 | 線性流轉,next后代碼后置執(zhí)行 | 洋蔥流轉,和Express同步表現一致 |
| 異步執(zhí)行邏輯 | next() 同步返回,不會等待異步;異步阻塞會直接跳出當前中間件,后置代碼提前執(zhí)行,流程錯亂 | await next() 阻塞等待全部下游異步完成,洋蔥順序永久穩(wěn)定 |
| 異步語法支持 | 原生不支持async/await,需回調嵌套,極易產生回調地獄 | 原生基于Promise+async/await,無嵌套 |
| 中間件底層實現 | 數組順序遍歷,線性瀑布模型 | compose遞歸調用,Promise鏈式洋蔥模型 |
| 上下文對象 | req、res分離,全局掛載數據容易沖突 | 統一ctx上下文,一次請求獨立ctx,數據隔離 |
| 異步錯誤捕獲 | 異步內部拋出錯誤無法被全局錯誤捕獲,必須手動try/catch傳err給next(err) | 全局可捕獲async中間件拋出的錯誤,統一error事件處理 |
| 多異步串聯 | 多層異步必須嵌套,可讀性極差 | 平鋪書寫,await順序執(zhí)行,邏輯清晰 |
四、異步錯誤處理對比(補充關鍵差異)
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 捕獲,自動觸發(fā)全局 error 事件。
五、總結核心結論
只寫同步代碼:Express 和 Koa 幾乎沒有區(qū)別,洋蔥/瀑布執(zhí)行效果一樣;
項目存在大量異步(數據庫、Redis、文件、接口) :二者出現本質鴻溝:
- Express:異步會打亂中間件執(zhí)行順序,回調嵌套、錯誤處理繁瑣;
- Koa:依靠
await next()固定洋蔥執(zhí)行流,異步代碼平鋪書寫,錯誤統一捕獲;
核心根源:
Express 中間件只是普通回調函數,無 Promise 封裝;
Koa 通過 compose 將所有中間件包裝成 Promise 調用鏈,讓異步流程可控。
到此這篇關于淺談Koa和Express的區(qū)別的文章就介紹到這了,更多相關Koa和Express區(qū)別內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
利用Chrome DevTools直接調試Node.js和JavaScript的方法詳解(并行)
現在我們可以用瀏覽器調試node.js了!!!下面這篇文章主要介紹了利用Chrome DevTools直接調試Node.js和JavaScript的方法步驟,文中介紹的很詳細,需要的朋友可以參考學習,下面來一起看看吧。2017-02-02
express搭建的nodejs項目使用webpack進行壓縮打包
對于打包這個問題它并不是難點,但是對于我們這種初學者來說,根本就不知道應該怎么做,下面這篇文章主要給大家介紹了關于express搭建的nodejs項目使用webpack進行壓縮打包的相關資料,需要的朋友可以參考下2022-12-12

