開啟服務(wù)器
const http = require('http');
const Koa = require('koa');
const app = new Koa();
app.listen(3000);//相當于 http.createServer(app.callback()).listen(3000);
中間件
Koa 的最大特色,也是最重要的一個設(shè)計,就是中間件(middleware)
- 中間件的概念
// Logger (打印日志)功能的實現(xiàn)
const logger = (ctx, next) => {
console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
next();
}
app.use(logger);
代碼中的logger函數(shù)就叫做"中間件"(middleware),因為它處在 HTTP Request 和 HTTP Response 中間,用來實現(xiàn)某種中間功能。app.use()用來加載中間件。
基本上,Koa 所有的功能都是通過中間件實現(xiàn)的,前面例子里面的main也是中間件。每個中間件默認接受兩個參數(shù),第一個參數(shù)是 Context 對象,第二個參數(shù)是next函數(shù)。只要調(diào)用next函數(shù),就可以把執(zhí)行權(quán)轉(zhuǎn)交給下一個中間件。
- 中間件棧
多個中間件會形成一個棧結(jié)構(gòu)(middle stack),以"先進后出"(first-in-last-out)的順序執(zhí)行。
1. 最外層的中間件首先執(zhí)行。
2. 調(diào)用next函數(shù),把執(zhí)行權(quán)交給下一個中間件。
3. ...
4. 最內(nèi)層的中間件最后執(zhí)行。
5. 執(zhí)行結(jié)束后,把執(zhí)行權(quán)交回上一層的中間件。
6. ...
7. 最外層的中間件收回執(zhí)行權(quán)之后,執(zhí)行next函數(shù)后面的代碼。
const one = (ctx, next) => {
console.log('>> one');
next();
console.log('<< one');
}
const two = (ctx, next) => {
console.log('>> two');
next();
console.log('<< two');
}
const three = (ctx, next) => {
console.log('>> three');
next();
console.log('<< three');
}
app.use(one);
app.use(two);
app.use(three);
輸出:
>> one
>> two
>> three
<< three
<< two
<< one
如果中間件內(nèi)部沒有調(diào)用next函數(shù),那么執(zhí)行權(quán)就不會傳遞下去。將two函數(shù)里面next()這一行注釋掉再執(zhí)行,會輸出:
>> one
>> two
<< two
<< one
- 中間件的合成
koa-compose模塊可以將多個中間件合成為一個。
const compose = require('koa-compose');
const logger = (ctx, next) => {
console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
next();
}
const main = ctx => {
ctx.response.body = 'Hello World';
};
const middlewares = compose([logger, main]);
app.use(middlewares);
錯誤處理
- 網(wǎng)頁錯誤
如果代碼運行過程中發(fā)生錯誤,我們需要把錯誤信息返回給用戶。HTTP 協(xié)定約定這時要返回500狀態(tài)碼。Koa 提供了ctx.throw()方法,用來拋出錯誤,ctx.throw(500)就是拋出500錯誤?;蛘邔?code>ctx.response.status設(shè)置成500,就相當于ctx.throw(500),返回500錯誤。但是如果要在try ... catch中捕獲異常,就要用ctx.throw(500),拋出異常。
const main = ctx => {
ctx.throw(500);
};
// 相當于
const main = ctx => {
ctx.response.status = 500;
ctx.response.body = 'Internal Server Error';
};
訪問會看到一個500錯誤頁"Internal Server Error"。
- 處理錯誤的中間件
為了方便處理錯誤,最好使用try...catch將其捕獲。但是,為每個中間件都寫try...catch太麻煩,我們可以讓最外層的中間件,負責所有中間件的錯誤處理。
const handler = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.response.status = err.statusCode || err.status || 500;
ctx.response.body = {
message: err.message
};
}
};
const main = ctx => {
ctx.throw(500);
};
app.use(handler);
app.use(main);
看到一個500頁,里面有報錯提示 {"message":"Internal Server Error"}
- error 事件的監(jiān)聽
運行過程中一旦出錯,Koa 會觸發(fā)一個error事件。監(jiān)聽這個事件,也可以處理錯誤。
const main = ctx => {
ctx.throw(500);
};
app.on('error', (err, ctx) => {
console.error('server error', err);
});
- 釋放 error 事件
需要注意的是,如果錯誤被try...catch捕獲,就不會觸發(fā)error事件。這時,必須調(diào)用ctx.app.emit(),手動釋放error事件,才能讓監(jiān)聽函數(shù)生效。
const handler = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.response.status = err.statusCode || err.status || 500;
ctx.response.type = 'html';
ctx.response.body = '<p>Something wrong, please contact administrator.</p>';
ctx.app.emit('error', err, ctx);
}
};
const main = ctx => {
// 需要用throw方法拋出錯誤
ctx.throw(500);
};
app.on('error', function(err) {
console.log('logging error ', err.message);
console.log(err);
});
上面代碼中,main函數(shù)拋出錯誤,被handler函數(shù)捕獲。catch代碼塊里面使用ctx.app.emit()手動釋放error事件,才能讓監(jiān)聽函數(shù)監(jiān)聽到。