您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關nodejs中間件Koa和Express有什區別,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
Koa用起來非常方便——比之express,它“完美中間件”的設計讓功能之間看起來非常簡潔!筆者在項目中就曾這樣使用過:
const Koa=require('koa') const app=new Koa() const Router=require('koa-router') const router=new Router() const cors=require('koa2-cors') const koaBody=require('koa-body') const ENV='test-mpin2' app.use(cors({ origin:['http://localhost:9528'], // 也可以寫為:['*'] credentials:true })) app.use(koaBody({ multipart:true })) app.use(async(ctx,next)=>{ console.log('訪問全局中間件') ctx.state.env=ENV // 全局緩存 await next() }) const playlist=require('./controller/playlist.js') router.use('/playlist',playlist.routes()) const blog=require('./controller/blog.js') router.use('/blog',blog.routes()) app.use(router.routes()).use(router.allowedMethods()) app.listen(3000,()=>{ console.log('服務已開啟') })
它將路由router抽離出去作為單獨的中間件使用,則app只負責全局處理。還比如:
// 最外層中間件,可以用于兜底 Koa 全局錯誤 app.use(async (ctx, next) => { try { // 執行下一個中間件 await next(); } catch (error) { console.log(`[koa error]: ${error.message}`) } }); // 第二層中間件,可以用于日志記錄 app.use(async (ctx, next) => { const { req } = ctx; console.log(`req is ${JSON.stringify(req)}`); await next(); console.log(`res is ${JSON.stringify(ctx.res)}`); });
簡單實現一個Koa吧!
如上代碼,我們看 Koa 實例,通過use方法注冊和串聯中間件,其源碼的簡單實現可以表述為:
use(fn) { this.middleware.push(fn); return this; }
我們將中間件存儲到this.middleware
數組中,那么中間件是如何被執行的呢?參考下面源碼:
// 通過 createServer 方法啟動一個 Node.js 服務 listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); }
Koa 框架通過 http 模塊的 createServer
方法創建一個 Node.js 服務,并傳入 this.callback()
方法, callback源碼簡單實現如下:
callback(){ const fn=compose(this.middlewareList) return (req,res)=>{ const ctx=createContext(req,res) return this.handleRequest(ctx,fn) } } handleRequest(ctx, fn) { const onerror = err => ctx.onerror(err); // 將 ctx 對象傳遞給中間件函數 fn return fn(ctx).catch(onerror); }
如上代碼,我們將 Koa 一個中間件組合和執行流程梳理為以下步驟:
通過一個方法(我們稱為compose)組合各種中間件,返回一個中間件組合函數fn
請求過來時,會先調用handleRequest
方法,該方法完成:
調用createContext
方法,對該次請求封裝出一個ctx對象;
接著調用this.handleRequest(ctx, fn)
處理該次請求。
其中,核心過程就是使用compose方法組合各種中間件 —— 這是一個單獨的方法,它應該不受Koa其余方法的約束。其源碼簡單實現為:
// 組合中間件 // 和express中的next函數意義一樣 function compose(middlewareList){ // return function意思是返回一個函數 return function(ctx,next){ // 各種中間件調用的邏輯 function dispatch(i){ const fn=middlewareList[i] || next if(fn){ try{ // koa中都是async,其返回的是一個promise(對象) return Promise.resolve(fn(ctx,function next(){ return dispatch(i+1) })) }catch(err){ return Promise.reject(err) } }else{ return Promise.resolve() } } return dispatch(0) } }
其功能可以表示為這樣(非源碼):
async function middleware1() { //... await (async function middleware2() { //... await (async function middleware3() { //... }); //... }); //... }
到這里我們其實可以“初窺”其原理,有兩點:
Koa 的中間件機制被社區形象地總結為洋蔥模型;
所謂洋蔥模型,就是指每一個 Koa 中間件都是一層洋蔥圈,它即可以掌管請求進入,也可以掌管響應返回。換句話說:外層的中間件可以影響內層的請求和響應階段,內層的中間件只能影響外層的響應階段。
dispatch(n)對應第 n 個中間件的執行,在使用中即第 n 個中間件可以通過await next()來“插入”執行下一個中間件,同時在最后一個中間件執行完成后,依然有恢復執行的能力。即:通過洋蔥模型,await next()控制調用后面的中間件,直到全局沒有可執行的中間件且堆棧執行完畢,最終“原路返回”至第一個執行next的中間件。這種方式有個優點,特別是對于日志記錄以及錯誤處理等全局功能需要非常友好。
Koa1 的中間件實現利用了 Generator 函數 + co 庫(一種基于 Promise 的 Generator 函數流程管理工具),來實現協程運行。本質上,Koa v1 中間件和 Koa v2 中間件思想是類似的,只不過 Koa v2 改用了 Async/Await 來替換 Generator 函數 + co 庫,整體實現更加巧妙,代碼更加優雅。—— from《狼書》
經過上述部分源碼的描述,我們就可以采用es6的方式將其組合起來:
// myKoa.js文件 const http=require('http') function compose(){} //見上 class LikeKoa2{ constructor() { this.middlewareList=[] } use(){} //見上 // 把所有的req,res屬性、事件都交給ctx(這里只是簡寫) createContext(req,res){ const ctx={ req, res } // 比如 ctx.query=req,query return ctx } handleRequest(){} //見上 callback(){} //見上 listen(){} //見上 } // koa和express的不同之一: // express在調用時直接調用函數:const app=express();所以暴露出去new過的對象——具體見下面鏈接中代碼 // 但是koa調用時以類的方式:const app=new Koa();所以直接暴露出去 module.exports=LikeKoa2
那use方法和其余方法并不相通,它是如何被執行的呢?執行了createServer后是不是相當于建立了一個通道、掛載了一個監聽函數呢?
這一點恐怕就要到Node的源碼中一探究竟了…
對比 Koa,聊聊 Express 原理
說起 Node.js 框架,我們一定忘不了 Express —— 不同于 Koa,它繼承了路由、靜態服務器和模板引擎等功能,雖然比之Koa顯得“臃腫”了許多,但看上去比 Koa 更像是一個框架。通過學習 Express 源碼,筆者簡單的總結了它的工作機制:
通過app.use方法注冊中間件。
一個中間件可以理解為一個 Layer 對象,其中包含了當前路由匹配的正則信息以及 handle 方法。
所有中間件(Layer 對象)使用stack數組存儲起來。
當一個請求過來時,會從 req 中獲取請求 path,根據 path 從stack中找到匹配的 Layer,具體匹配過程由router.handle
函數實現。
router.handle
函數通過next()
方法遍歷每一個 layer 進行比對:
next()
方法通過閉包維持了對于 Stack Index 游標的引用,當調用next()
方法時,就會從下一個中間件開始查找;
如果比對結果為 true,則調用layer.handle_request
方法,layer.handle_request
方法中會調用next()方法 ,實現中間件的執行。
通過上述內容,我們可以看到,Express 其實是通過 next()
方法維護了遍歷中間件列表的 Index 游標,中間件每次調用next()
方法時,會通過增加 Index 游標的方式找到下一個中間件并執行。它的功能就像這樣:
((req, res) => { console.log('第一個中間件'); ((req, res) => { console.log('第二個中間件'); (async(req, res) => { console.log('第三個中間件'); await sleep(2000) res.status(200).send('hello') })(req, res) console.log('第二個中間件調用結束'); })(req, res) console.log('第一個中間件調用結束') })(req, res)
如上代碼,Express 中間件設計并不是一個洋蔥模型,它是基于回調實現的線形模型,不利于組合,不利于互操,在設計上并不像 Koa 一樣簡單。而且業務代碼有一定程度的侵擾,甚至會造成不同中間件間的耦合。
express的簡單實現筆者已上傳至騰訊微云,需要者可自行查看&下載:express的簡單實現
關于“nodejs中間件Koa和Express有什區別”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。