JavaScript 實現(xiàn)類似Express的中間件系統(tǒng)(實例詳解)
Express 的中間件系統(tǒng)
在 Express 中可以給一個請求設(shè)置若干個中間件,在處理響應(yīng)時會按順序執(zhí)行這些中間件,正在執(zhí)行的中間件可以控制是否執(zhí)行下一個中間件。
模擬實現(xiàn)的 Express 將擁有這些功能:
- Express 類擁有三個實例方法:
run(url)開始執(zhí)行中間件,接收 url。 use(fn)設(shè)置應(yīng)用中間件,在路由中間件之前執(zhí)行。get(url, fn)設(shè)置路由中間件,只在url與請求路由一致時執(zhí)行。fn的定義為:(req:any, res:any, next) => void。- 在
fn中調(diào)用next()方法執(zhí)行下一個中間件。 - 在
fn中調(diào)用res.end(response)方法后將本次響應(yīng)值為response并且不再執(zhí)行后續(xù)中間件。 - req 和 res 會逐層傳遞,可以被修改。
- 調(diào)用
run(url)方法后開始執(zhí)行中間件。 - 如果沒有注冊對應(yīng)的路由中間件,則只執(zhí)行應(yīng)用中間件。
- 如果有對應(yīng)的路由中間件,則先執(zhí)行應(yīng)用中間件后執(zhí)行路由中間件。
- 中間件按照注冊順序執(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");實現(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);
}
}
如何實現(xiàn)異步執(zhí)行鏈
在調(diào)用 use() 方法或 get() 方法設(shè)置中間件時,將傳入的回調(diào)函數(shù)包裝成一個返回 Promise 對象的新函數(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() 方法時,先根據(jù)傳入的 url 決定要執(zhí)行的中間件,然后遍歷中間件列表,拼接成一個 Promise 鏈。
let promise = Promise.resolve(ctx);
chain.forEach((middleware) => {
promise = promise.then(middleware);
});如何將控制權(quán)交給中間件函數(shù)
wrap() 方法中,給實際的中間件函數(shù)傳遞一個方法,這個方法調(diào)用后 wrap() 返回的 Promise 才會被 resolve,這個方法就是 next() 方法。
const result = fn(ctx.req, ctx.res, () => {
resolve(ctx);
});使用示例
應(yīng)用級中間件與路由級中間件
用 use() 綁定的中間件為應(yīng)用級中間件,用 get() 綁定的為路由級中間件。應(yīng)用級中間件總是會執(zhí)行,只調(diào)用 .run() 方法接收到的對應(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() 方法返回一個 Promise 對象,如果所有中間件都執(zhí)行完畢無異常則返回 res 對象:
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() 方法,則會停止執(zhí)行后續(xù)中間件,并將拋出的異常對象(或 res.end() 的參數(shù))作為 Promise 的失敗原因。
app.use(function (req, res, next) {
res.name = "zhangkb";
res.end(new Error("reason")); // 主動停止
throw new Error("reason"); // 拋出異常
next();
});
app.run("/").catch((error) => {
console.log("error", error);
});到此這篇關(guān)于JavaScript 實現(xiàn)類似Express的中間件系統(tǒng)的文章就介紹到這了,更多相關(guān)js Express的中間件系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js學(xué)習(xí)總結(jié)_輪播圖之漸隱漸現(xiàn)版(實例講解)
下面小編就為大家?guī)硪黄猨s學(xué)習(xí)總結(jié)_輪播圖之漸隱漸現(xiàn)版(實例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
ES6新數(shù)據(jù)結(jié)構(gòu)Set與WeakSet用法分析
這篇文章主要介紹了ES6新數(shù)據(jù)結(jié)構(gòu)Set與WeakSet用法,結(jié)合實例形式簡單分析了Set與WeakSet的功能、使用方法及相關(guān)注意事項,需要的朋友可以參考下2017-03-03

