摘要:應(yīng)用級(jí)別頂層處理在上面中間件執(zhí)行時(shí)看到,會(huì)自動(dòng)幫我們捕獲錯(cuò)誤并處理,如下捕獲錯(cuò)誤在中處理我們看發(fā)現(xiàn)它事實(shí)上是出發(fā)監(jiān)聽(tīng)的事件假如我們沒(méi)有定義回調(diào)怎么辦呢,也為我們定義了默認(rèn)的錯(cuò)誤處理函數(shù)方法做了判斷全文完
koa原理淺析
選取的版本為koa2原文鏈接
koa的源碼由四個(gè)文件組成
application.js koa的骨架 context.js ctx的原型 request.js request的原型 response.js response的原型基本用法
const Koa = require("koa"); const app = new Koa(); app.use(async ctx => { ctx.body = "Hello World"; }); app.listen(3000);初始服務(wù)器
利用http模塊創(chuàng)建服務(wù)器
const app = http.createServer((req, res) => { ... }) app.listen(3000)
事實(shí)上koa把這些包在了其listen方法中
listen(...args) { debug("listen"); const server = http.createServer(this.callback()); return server.listen(...args); }
顯然this.callback()返回的是一個(gè)形如下面的函數(shù)
(req, res) => {}上下文ctx
callback方法如下
callback() { const fn = compose(this.middleware); if (!this.listeners("error").length) this.on("error", this.onerror); const handleRequest = (req, res) => { const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); }; return handleRequest; }
ctx在koa中事實(shí)上是一個(gè)包裝了request和response的對(duì)象,從createContext中可以看到起繼承自context
createContext(req, res) { const context = Object.create(this.context); const request = context.request = Object.create(this.request); const response = context.response = Object.create(this.response); context.app = request.app = response.app = this; context.req = request.req = response.req = req; context.res = request.res = response.res = res; request.ctx = response.ctx = context; request.response = response; response.request = request; context.originalUrl = request.originalUrl = req.url; context.cookies = new Cookies(req, res, { keys: this.keys, secure: request.secure }); request.ip = request.ips[0] || req.socket.remoteAddress || ""; context.accept = request.accept = accepts(req); context.state = {}; return context; }
可以看到ctx.request繼承自request,ctx.response繼承自response,查看response和request可以看到里面大都是set和get方法(獲取query,設(shè)置header)等等。并且ctx代理了ctx.request和ctx.response的方法,在源碼中可以看到
delegate(proto, "response") .method("attachment") .method("redirect") .method("remove") .method("vary") .method("set") .method("append") .method("flushHeaders") .access("status") .access("message") .access("body") .access("length") .access("type") .access("lastModified") .access("etag") .getter("headerSent") .getter("writable"); /** * Request delegation. */ delegate(proto, "request") .method("acceptsLanguages") .method("acceptsEncodings") .method("acceptsCharsets") .method("accepts") .method("get") .method("is") .access("querystring") .access("idempotent") .access("socket") .access("search") .access("method") .access("query") .access("path") .access("url") .getter("origin") .getter("href") .getter("subdomains") .getter("protocol") .getter("host") .getter("hostname") .getter("URL") .getter("header") .getter("headers") .getter("secure") .getter("stale") .getter("fresh") .getter("ips") .getter("ip");
所以我們可以直接這么寫(xiě)
ctx.url 等價(jià)于 ctx.request.url中間件
我們?cè)倏匆幌耤allback函數(shù),觀察發(fā)現(xiàn)compose模塊十分的神奇,我暫且把它稱(chēng)為是一個(gè)迭代器,它實(shí)現(xiàn)了中間件的順序執(zhí)行
const fn = compose(this.middleware); 打印fn如下 function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error("next() called multiple times")) index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } }
最初接觸koa的時(shí)候我疑惑為什么我寫(xiě)了
ctx.body = "hello world"
并沒(méi)有ctx.response.end()之類(lèi)的方法,事實(shí)上koa已經(jīng)幫我們做了處理,在handleRequest方法中
const handleResponse = () => respond(ctx); // fnMiddleware即為上面compose之后的fn fnMiddleware(ctx).then(handleResponse).catch(onerror)
fnMiddleware返回的是一個(gè)promise,在中間件邏輯完成后在respond函數(shù)中最終去處理ctx.body
function respond(ctx) { // allow bypassing koa if (false === ctx.respond) return; const res = ctx.res; if (!ctx.writable) return; let body = ctx.body; const code = ctx.status; // ignore body if (statuses.empty[code]) { // strip headers ctx.body = null; return res.end(); } if ("HEAD" == ctx.method) { if (!res.headersSent && isJSON(body)) { ctx.length = Buffer.byteLength(JSON.stringify(body)); } return res.end(); } // status body if (null == body) { body = ctx.message || String(code); if (!res.headersSent) { ctx.type = "text"; ctx.length = Buffer.byteLength(body); } return res.end(body); } // responses if (Buffer.isBuffer(body)) return res.end(body); if ("string" == typeof body) return res.end(body); if (body instanceof Stream) return body.pipe(res); // body: json body = JSON.stringify(body); if (!res.headersSent) { ctx.length = Buffer.byteLength(body); } res.end(body); }錯(cuò)誤處理
(非首部)中間件層處理(我瞎起的)
對(duì)于每個(gè)中間件可能發(fā)生的錯(cuò)誤,可以直接在該中間件捕獲
app.use((ctx, next) => { try { ... } catch(err) { ... } })
(首部)中間件層處理
事實(shí)上,我們只要在第一個(gè)中間件添加try... catch... ,整個(gè)中間件組的錯(cuò)誤都是可以捕獲的到的。
(應(yīng)用級(jí)別)頂層處理
app.on("error", (err) = {})
在上面中間件執(zhí)行時(shí)看到,koa會(huì)自動(dòng)幫我們捕獲錯(cuò)誤并處理,如下
try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { // 捕獲錯(cuò)誤 return Promise.reject(err) } // 在ctx.onerror中處理 const onerror = err => ctx.onerror(err); fnMiddleware(ctx).then(handleResponse).catch(onerror)
我們看ctx.onerror發(fā)現(xiàn)它事實(shí)上是出發(fā)app監(jiān)聽(tīng)的error事件
onerror(err) { // delegate this.app.emit("error", err, this);
假如我們沒(méi)有定義error回調(diào)怎么辦呢,koa也為我們定義了默認(rèn)的錯(cuò)誤處理函數(shù)
callback方法做了判斷
callback() { ... if (!this.listeners("error").length) this.on("error", this.onerror); ... }
全文完
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/92098.html
摘要:?jiǎn)?dòng)流程主要的啟動(dòng)流程就是下面的步引入包實(shí)例化編寫(xiě)中間件監(jiān)聽(tīng)服務(wù)器引入包引入包其實(shí)就是引入的一個(gè)繼承于原生的類(lèi)的類(lèi)其中就包含了等原型方法實(shí)例化執(zhí)行,將等對(duì)象封裝在實(shí)例中編寫(xiě)中間件首先判斷的類(lèi)型,不是方法直接拋錯(cuò)是生成器函數(shù)的話用封裝是函數(shù) 啟動(dòng)流程 koa 主要的啟動(dòng)流程就是下面的 4 步:引入 koa 包 => 實(shí)例化 koa => 編寫(xiě)中間件 => 監(jiān)聽(tīng)服務(wù)器 const koa ...
摘要:任何一層報(bào)錯(cuò),都能用捕獲總結(jié)是一個(gè)非常輕量級(jí)的框架,只實(shí)現(xiàn)了中間件處理流程和對(duì)對(duì)象的封裝。其他的功能都由外部中間件提供。 koa 的中間件機(jī)制巧妙的運(yùn)用了閉包和 async await 的特點(diǎn),形成了一個(gè)洋蔥式的流程,和 JS 的事件流 (捕獲 -> target -> 冒泡) 相似 handleRequest(ctx, fnMiddleware) { const res ...
摘要:代碼結(jié)構(gòu)執(zhí)行流程上面兩張圖主要將的整體代碼結(jié)構(gòu)和大概的執(zhí)行流程畫(huà)了出來(lái),畫(huà)的不夠具體。那下面主要講中的幾處的關(guān)鍵代碼解讀一下。全局的路由參數(shù)處理的中間件組成的對(duì)象。 代碼結(jié)構(gòu) showImg(https://segmentfault.com/img/remote/1460000007468236?w=1425&h=1772); 執(zhí)行流程 showImg(https://segmentf...
摘要:前言被認(rèn)為是第二代,它最大的特點(diǎn)就是獨(dú)特的中間件流程控制,是一個(gè)典型的洋蔥模型。這段代碼就很巧妙的實(shí)現(xiàn)了兩點(diǎn)將一路傳下去給中間件將中的下一個(gè)中間件作為未來(lái)的返回值這兩點(diǎn)也是洋蔥模型實(shí)現(xiàn)的核心。 前言 koa被認(rèn)為是第二代node web framework,它最大的特點(diǎn)就是獨(dú)特的中間件流程控制,是一個(gè)典型的洋蔥模型。koa和koa2中間件的思路是一樣的,但是實(shí)現(xiàn)方式有所區(qū)別,koa2在...
摘要:當(dāng)運(yùn)行到時(shí),不會(huì)暫停,而是直接跳進(jìn)函數(shù)執(zhí)行函數(shù)內(nèi)的代碼。由于函數(shù)中沒(méi)有,因此會(huì)一直執(zhí)行完函數(shù)中的代碼,并返回至函數(shù)中執(zhí)行后面的代碼。 本系列旨在通過(guò)對(duì)co,koa等庫(kù)源碼的研究,進(jìn)而理解generator在異步編程中的重大作用(ps:所有代碼請(qǐng)?jiān)趎ode --harmony或者iojs環(huán)境中運(yùn)行) koa中間件的形式 相信用過(guò)koa的小伙伴一定很熟悉下面這段代碼 var app ...
閱讀 1896·2021-11-11 16:55
閱讀 2106·2021-10-08 10:13
閱讀 755·2019-08-30 11:01
閱讀 2166·2019-08-29 13:19
閱讀 3293·2019-08-28 18:18
閱讀 2631·2019-08-26 13:26
閱讀 588·2019-08-26 11:40
閱讀 1879·2019-08-23 17:17