首頁 >web前端 >js教程 >比較一下nodejs中間件Koa和Express

比較一下nodejs中間件Koa和Express

青灯夜游
青灯夜游轉載
2021-02-24 09:52:142357瀏覽

比較一下nodejs中間件Koa和Express

相關推薦:《nodejs 教學

說到中間件,許多開發者都會想到Koa.js,其中間件設計無疑是前端中間件思想的典型代表之一。

最近重新溫習這部分內容,按奈不住想要和各位看官聊聊其中絕妙!


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的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除