JavaScript 實(shí)現(xiàn)類似Express的中間件系統(tǒng)(實(shí)例詳解)
Express 的中間件系統(tǒng)
在 Express 中可以給一個(gè)請(qǐng)求設(shè)置若干個(gè)中間件,在處理響應(yīng)時(shí)會(huì)按順序執(zhí)行這些中間件,正在執(zhí)行的中間件可以控制是否執(zhí)行下一個(gè)中間件。
模擬實(shí)現(xiàn)的 Express 將擁有這些功能:
- Express 類擁有三個(gè)實(shí)例方法:
run(url)
開(kāi)始執(zhí)行中間件,接收 url。 use(fn)
設(shè)置應(yīng)用中間件,在路由中間件之前執(zhí)行。get(url, fn)
設(shè)置路由中間件,只在url
與請(qǐng)求路由一致時(shí)執(zhí)行。fn
的定義為:(req:any, res:any, next) => void
。- 在
fn
中調(diào)用next()
方法執(zhí)行下一個(gè)中間件。 - 在
fn
中調(diào)用res.end(response)
方法后將本次響應(yīng)值為response
并且不再執(zhí)行后續(xù)中間件。 - req 和 res 會(huì)逐層傳遞,可以被修改。
- 調(diào)用
run(url)
方法后開(kāi)始執(zhí)行中間件。 - 如果沒(méi)有注冊(cè)對(duì)應(yīng)的路由中間件,則只執(zhí)行應(yīng)用中間件。
- 如果有對(duì)應(yīng)的路由中間件,則先執(zhí)行應(yīng)用中間件后執(zhí)行路由中間件。
- 中間件按照注冊(cè)順序執(zhí)行。
大概的使用方式如下:
class Express {} const app = new Express(); app.use(function (req, res, next) { console.log("mid"); next(); }); app.use(function (req, res, next) { console.log("mid"); next(); }); app.get("/home", function (req, res, next) { console.log("page home"); next(); }); app.get("/detail", function (req, res, next) { console.log("page detail"); next(); }); app.run("/home");
實(shí)現(xiàn)代碼
const appMiddlewareKey = Symbol("app_middleware"); class Express { constructor() { this.middleware = {}; } use(fn) { this.get(appMiddlewareKey, fn); } get(url, fn) { if (!this.middleware[url]) { this.middleware[url] = []; } function wrap(ctx) { return new Promise((resolve, reject) => { ctx.res.end = (response) => { reject(response); }; try { const result = fn(ctx.req, ctx.res, () => { resolve(ctx); }); if (result instanceof Promise) { result.catch(reject); } } catch (error) { reject(error); } }); } this.middleware[url].push(wrap); } run(url) { const chain = [].concat(this.middleware[appMiddlewareKey]); const route = this.middleware[url]; if (route) { chain.push(...route); } const ctx = { req: { url, }, res: {}, }; let promise = Promise.resolve(ctx); chain.forEach((middleware) => { promise = promise.then(middleware); }); return promise.then((ctx) => ctx.res); } }
如何實(shí)現(xiàn)異步執(zhí)行鏈
在調(diào)用 use()
方法或 get()
方法設(shè)置中間件時(shí),將傳入的回調(diào)函數(shù)包裝成一個(gè)返回 Promise 對(duì)象的新函數(shù)。
function wrap(ctx) { return new Promise((resolve, reject) => { // 提供停止執(zhí)行中間件的方法 ctx.res.end = (response) => { reject(response); }; try { const result = fn(ctx.req, ctx.res, () => { resolve(ctx); }); if (result instanceof Promise) { result.catch(reject); // 如果傳入中間件回調(diào)返回值為 Promise } } catch (error) { reject(error); } }); } this.middleware[url].push(wrap);
在調(diào)用 .run()
方法時(shí),先根據(jù)傳入的 url
決定要執(zhí)行的中間件,然后遍歷中間件列表,拼接成一個(gè) Promise 鏈。
let promise = Promise.resolve(ctx); chain.forEach((middleware) => { promise = promise.then(middleware); });
如何將控制權(quán)交給中間件函數(shù)
wrap()
方法中,給實(shí)際的中間件函數(shù)傳遞一個(gè)方法,這個(gè)方法調(diào)用后 wrap()
返回的 Promise 才會(huì)被 resolve,這個(gè)方法就是 next()
方法。
const result = fn(ctx.req, ctx.res, () => { resolve(ctx); });
使用示例
應(yīng)用級(jí)中間件與路由級(jí)中間件
用 use()
綁定的中間件為應(yīng)用級(jí)中間件,用 get()
綁定的為路由級(jí)中間件。應(yīng)用級(jí)中間件總是會(huì)執(zhí)行,只調(diào)用 .run()
方法接收到的對(duì)應(yīng) url 的路由中間件。
以下例子不調(diào)用 /detail
的路由中間件。
app.use(function (req, res, next) { res.name = "zhangkb"; next(); }); app.get("/home", function (req, res, next) { console.log("page home", req); next(); }); app.get("/detail", function (req, res, next) { console.log("page home", req); next(); }); app.run("/home");
.run()
的返回值與異常處理
.run()
方法返回一個(gè) Promise 對(duì)象,如果所有中間件都執(zhí)行完畢無(wú)異常則返回 res 對(duì)象:
app.use(function (req, res, next) { res.name = "zhangkb"; next(); }); app.run("/").then((res) => { console.log("success", res); });
如果在中間件中用 throw
關(guān)鍵字拋出異?;蛘哒{(diào)用 res.end()
方法,則會(huì)停止執(zhí)行后續(xù)中間件,并將拋出的異常對(duì)象(或 res.end()
的參數(shù))作為 Promise 的失敗原因。
app.use(function (req, res, next) { res.name = "zhangkb"; res.end(new Error("reason")); // 主動(dòng)停止 throw new Error("reason"); // 拋出異常 next(); }); app.run("/").catch((error) => { console.log("error", error); });
到此這篇關(guān)于JavaScript 實(shí)現(xiàn)類似Express的中間件系統(tǒng)的文章就介紹到這了,更多相關(guān)js Express的中間件系統(tǒng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js學(xué)習(xí)總結(jié)_輪播圖之漸隱漸現(xiàn)版(實(shí)例講解)
下面小編就為大家?guī)?lái)一篇js學(xué)習(xí)總結(jié)_輪播圖之漸隱漸現(xiàn)版(實(shí)例講解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07快速對(duì)接payjq的個(gè)人微信支付接口過(guò)程解析
這篇文章主要介紹了快速對(duì)接payjq的個(gè)人微信支付接口過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08ES6新數(shù)據(jù)結(jié)構(gòu)Set與WeakSet用法分析
這篇文章主要介紹了ES6新數(shù)據(jù)結(jié)構(gòu)Set與WeakSet用法,結(jié)合實(shí)例形式簡(jiǎn)單分析了Set與WeakSet的功能、使用方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-03-03js實(shí)現(xiàn)簡(jiǎn)單的倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)簡(jiǎn)單的倒計(jì)時(shí),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01