koa2异常处理_读 koa2 源码后的一些思考与实践
koa2的特點(diǎn)優(yōu)勢(shì)
什么是 koa2
koa2 的優(yōu)點(diǎn)
優(yōu)點(diǎn)這個(gè)東西,我直接說(shuō)它多好,你可能又不開(kāi)心,但是我們可以對(duì)比哦!這里我只說(shuō)它對(duì)比原生的 Node.js開(kāi)啟 http 服務(wù) 帶來(lái)了哪些優(yōu)點(diǎn)!
- 先看一下原生 Node.js 我開(kāi)啟一個(gè) http 服務(wù)
- 看一下使用 koa2 開(kāi)啟一個(gè)http 服務(wù)
我在 koa2 中添加了一個(gè)判斷 /favicon.ico 的實(shí)現(xiàn) 通過(guò)以上兩段代碼,會(huì)發(fā)現(xiàn)下面幾個(gè)優(yōu)點(diǎn)
自己實(shí)現(xiàn)一個(gè)koa2
在實(shí)現(xiàn)的過(guò)程中會(huì)我看看可以學(xué)到那些知識(shí)
listen 函數(shù)簡(jiǎn)單封裝
koa2 直接使用的時(shí)候,我們通過(guò) const app = new Koa();,koa 應(yīng)該是一個(gè)類,而且可以直接調(diào)用 listen 函數(shù),并且沒(méi)有暴漏出http 服務(wù)的創(chuàng)建,說(shuō)明在listen函數(shù)中可能創(chuàng)建了服務(wù)。到此簡(jiǎn)單代碼實(shí)現(xiàn)應(yīng)該是這樣的:
class Kkb{ constructor(){ this.middlewares = []; } listen(...args){ http.createServer(async (req,res)=>{ // 給用戶返回信息 this.callback(req,res); res.writeHead(200); res.statusCode = 200; res.end('hello koala') }).listen(...args) }}module.exports = Kkb;實(shí)現(xiàn) context 的封裝
實(shí)現(xiàn)了簡(jiǎn)單 listen 后,會(huì)發(fā)現(xiàn)回調(diào)函數(shù)返回的還是 req 和 res ,要是將二者封裝到 context 一次返回就更好了!我們繼續(xù)
const ctx = this.createContext(req,res);看一下 createContext 的具體實(shí)現(xiàn)
const request = require('./lib/request');const response = require('./lib/response');const context = require('./lib/context'); createContext(req,res){ // 創(chuàng)建一個(gè)新對(duì)象,繼承導(dǎo)入的context const ctx = Object.create(context); ctx.request = Object.create(request); ctx.response = Object.create(response); // 這里的兩等于判斷,讓使用者既可以直接使用ctx,也可以使用原生的內(nèi)容 ctx.req = ctx.request.req = req; ctx.res = ctx.response.res = res; return ctx; }context.js
module.exports = { get url(){ return this.request.url; }, get body(){ return this.response.body; }, set body(val){ this.response.body = val; }}request.js
module.exports = { get url(){ return this.req.url; }}這里在寫(xiě) context.js 時(shí)候,用到了set 與 get 函數(shù),get 語(yǔ)句作為函數(shù)綁定在對(duì)象的屬性上,當(dāng)訪問(wèn)該屬性時(shí)調(diào)用該函數(shù)。set 語(yǔ)法可以將一個(gè)函數(shù)綁定在當(dāng)前對(duì)象的指定屬性上,當(dāng)那個(gè)屬性被賦值時(shí),你所綁定的函數(shù)就會(huì)被調(diào)用。
實(shí)現(xiàn)洋蔥模型
compose 另一個(gè)應(yīng)用場(chǎng)景
說(shuō)洋蔥模型之前先看一個(gè)函數(shù)式編程內(nèi)容:compose 函數(shù)前端用過(guò) redux 的同學(xué)肯定都很熟悉。redux 通過(guò)compose來(lái)處理 中間件 。原理是 借助數(shù)組的 reduce 對(duì)數(shù)組的參數(shù)進(jìn)行迭代
// redux 中的 compose 函數(shù)export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args)))}洋蔥模型實(shí)現(xiàn)
再看文章開(kāi)頭 koa2 創(chuàng)建 http 服務(wù)函數(shù),會(huì)發(fā)現(xiàn)多次調(diào)用 use 函數(shù),其實(shí)這就是洋蔥模型的應(yīng)用。
洋蔥是由很多層組成的,你可以把每個(gè)中間件看作洋蔥里的一層,根據(jù)app.use的調(diào)用順序中間件由外層到里層組成了整個(gè)洋蔥,整個(gè)中間件執(zhí)行過(guò)程相當(dāng)于由外到內(nèi)再到外地穿透整個(gè)洋蔥
引用一張著名的洋蔥模型圖:
每次執(zhí)行 use 函數(shù),我們實(shí)際是往一個(gè)函數(shù)數(shù)組中添加了一個(gè)函數(shù),然后再次通過(guò)一個(gè) compose 函數(shù),處理添加進(jìn)來(lái)函數(shù)的執(zhí)行順序,也就是這個(gè) compose 函數(shù)實(shí)現(xiàn)了洋蔥模型機(jī)制。
具體代碼實(shí)現(xiàn)如下:
// 其中包含一個(gè)遞歸 compose(middlewares){ return async function(ctx){// 傳入上下文 return dispatch(0); function dispatch(i){ let fn = middlewares[i]; if(!fn){ return Promise.resolve(); } return Promise.resolve( fn(ctx,function next(){ return dispatch(i+1) }) ) } } }首先執(zhí)行一次 dispatch(0) 也就是默認(rèn)返回第一個(gè) app.use 傳入的函數(shù) 使用 Promise 函數(shù)封裝返回,其中第一個(gè)參數(shù)是我們常用的 ctx,
第二個(gè)參數(shù)就是 next 參數(shù),next 每次執(zhí)行之后都會(huì)等于下一個(gè)中間件函數(shù),如果下一個(gè)中間件函數(shù)不為真則返回一個(gè)成功的 Promise。因此我們每次調(diào)用 next() 就是在執(zhí)行下一個(gè)中間件函數(shù)。
來(lái)試試我們自己實(shí)現(xiàn)的koa2
使用一下我們自己的 koa2 吧,用它做一道常考洋蔥模型面試題,我想文章如果懂了,輸出結(jié)果應(yīng)該不會(huì)錯(cuò)了,自己試一下!
const KKB = require('./kkb');const app = new KKB();app.use(async (ctx,next)=>{ ctx.body = '1'; await next(); ctx.body += '3';})app.use(async (ctx,next)=>{ ctx.body += '4'; await delay(); await next(); ctx.body += '5';})app.use(async (ctx,next)=>{ ctx.body += '6'})async function delay(){ return new Promise((reslove,reject)=>{ setTimeout(()=>{ reslove(); },1000); })}app.listen(3000);解題思路:還是洋蔥思想,洋蔥是先從皮到心,然后從心到皮
答案: 1 4 6 5 3
補(bǔ)充與說(shuō)明
本文目的主要是讓大家學(xué)到一個(gè)koa2的基本流程,簡(jiǎn)單實(shí)現(xiàn)koa2,再去讀源碼有一個(gè)清晰的思路。實(shí)際源碼中還有很多優(yōu)秀的值得我們學(xué)習(xí)的點(diǎn),接下來(lái)再列舉一個(gè)我覺(jué)得它很優(yōu)秀的點(diǎn)——錯(cuò)誤處理,大家可在原有基礎(chǔ)上繼續(xù)實(shí)現(xiàn),也可以去讀源碼繼續(xù)看!加油加油
源碼中 koa 繼承自 Emiiter,為了處理可能在任意時(shí)間拋出的異常所以訂閱了 error 事件。error 處理有兩個(gè)層面,一個(gè)是 app 層面全局的(主要負(fù)責(zé) log),另一個(gè)是一次響應(yīng)過(guò)程中的 error 處理(主要決定響應(yīng)的結(jié)果),koa 有一個(gè)默認(rèn) app-level 的 onerror 事件,用來(lái)輸出錯(cuò)誤日志。
// 在調(diào)用洋蔥模型函數(shù)后面,koa 會(huì)掛載一個(gè)默認(rèn)的錯(cuò)誤處理【運(yùn)行時(shí)確定異常處理】 if (!this.listenerCount("error")) this.on("error", this.onerror); onerror(err) { if (!(err instanceof Error)) throw new TypeError(util.format("non-error thrown: %j", err)); if (404 == err.status || err.expose) return; if (this.silent) return; const msg = err.stack || err.toString(); console.error(); console.error(msg.replace(/^/gm, " ")); console.error(); }通過(guò) Emiiter 實(shí)現(xiàn)了錯(cuò)誤打印,Emiiter 采用了發(fā)布訂閱的設(shè)計(jì)模式,如果有對(duì) Emiiter 有不太清楚的小伙伴可以看我這篇文章
[源碼解讀]一文徹底搞懂Events模塊
。
總結(jié)
本文注重思想,精簡(jiǎn)版本,代碼與實(shí)現(xiàn)都很簡(jiǎn)單。封裝,遞歸,設(shè)計(jì)模式都說(shuō)了一丟丟,希望也能對(duì)你有一丟丟的提升和讓你去看一下koa2源碼的想法,下篇文章見(jiàn)。
▼ 原創(chuàng)系列推薦▼
TypeScript真香系列——接口篇
消息隊(duì)列助你成為高薪 Node.js 工程師
深入理解Node.js 進(jìn)程與線程(8000長(zhǎng)文徹底搞懂)
[源碼解讀]一文徹底搞懂Events模塊
Node.js 高級(jí)進(jìn)階之 fs 文件模塊學(xué)習(xí)
Node進(jìn)階-探究不在V8堆內(nèi)存中存儲(chǔ)的Buffer對(duì)象
說(shuō)Node.js做后端開(kāi)發(fā),stream有必要了解下
點(diǎn)在看,分享給身邊的開(kāi)發(fā)
總結(jié)
以上是生活随笔為你收集整理的koa2异常处理_读 koa2 源码后的一些思考与实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 梦到自己牙齿掉光了是什么意思
- 下一篇: 8 一点就消失_消失的莉莉安(26)