您好,登錄后才能下訂單哦!
前言
koa.js是最流行的node.js后端框架之一,有很多網(wǎng)站都使用koa進(jìn)行開發(fā),同時(shí)社區(qū)也涌現(xiàn)出了一大批基于koa封裝的企業(yè)級(jí)框架。然而,在這些亮眼的成績(jī)背后,作為核心引擎的koa代碼庫本身,卻非常的精簡(jiǎn),不得不讓人驚嘆于其巧妙的設(shè)計(jì)。
在平時(shí)的工作開發(fā)中,筆者是koa的重度用戶,因此對(duì)其背后的原理自然也是非常感興趣,因此在閑暇之余進(jìn)行了研究。不過本篇文章,并不是源碼分析,而是從相反的角度,向大家展示如何從頭開發(fā)實(shí)現(xiàn)一個(gè)koa框架,在這個(gè)過程中,koa中最重要的幾個(gè)概念和原理都會(huì)得到展現(xiàn)。相信大家在看完本文之后,會(huì)對(duì)koa有一個(gè)更深入的理解,同時(shí)在閱讀本文之后再去閱讀koa源碼,思路也將非常的順暢。
首先放出筆者實(shí)現(xiàn)的這個(gè)koa框架代碼庫地址:simpleKoa
需要說明的是,本文實(shí)現(xiàn)的koa是koa 2版本,也就是基于async/await的,因此需要node版本在7.6以上。如果讀者的node版本較低,建議升級(jí),或者安裝babel-cli,利用其中的babel-node來運(yùn)行例子。
四條主線
筆者認(rèn)為,理解koa,主要需要搞懂四條主線,其實(shí)也是實(shí)現(xiàn)koa的四個(gè)步驟,分別是
下面就一一進(jìn)行分析。
主線一:封裝node http Server: 從hello world說起
首先,不考慮框架,如果使用原生http模塊來實(shí)現(xiàn)一個(gè)返回hello world的后端app,代碼如下:
let http = require('http'); let server = http.createServer((req, res) => { res.writeHead(200); res.end('hello world'); }); server.listen(3000, () => { console.log('listenning on 3000'); });
實(shí)現(xiàn)koa的第一步,就是對(duì)這個(gè)原生的過程進(jìn)行封裝,為此,我們首先創(chuàng)建application.js實(shí)現(xiàn)一個(gè)Application對(duì)象:
// application.js let http = require('http'); class Application { /** * 構(gòu)造函數(shù) */ constructor() { this.callbackFunc; } /** * 開啟http server并傳入callback */ listen(...args) { let server = http.createServer(this.callback()); server.listen(...args); } /** * 掛載回調(diào)函數(shù) * @param {Function} fn 回調(diào)處理函數(shù) */ use(fn) { this.callbackFunc = fn; } /** * 獲取http server所需的callback函數(shù) * @return {Function} fn */ callback() { return (req, res) => { this.callbackFunc(req, res); }; } } module.exports = Application;
然后創(chuàng)建example.js:
let simpleKoa = require('./application'); let app = new simpleKoa(); app.use((req, res) => { res.writeHead(200); res.end('hello world'); }); app.listen(3000, () => { console.log('listening on 3000'); });
可以看到,我們已經(jīng)初步完成了對(duì)于http server的封裝,主要實(shí)現(xiàn)了app.use注冊(cè)回調(diào)函數(shù),app.listen語法糖開啟server并傳入回調(diào)函數(shù)了,典型的koa風(fēng)格。
但是美中不足的是,我們傳入的回調(diào)函數(shù),參數(shù)依然使用的是req和res,也就是node原生的request和response對(duì)象,這些原生對(duì)象和api提供的方法不夠便捷,不符合一個(gè)框架需要提供的易用性。因此,我們需要進(jìn)入第二條主線了。
主線二:構(gòu)造request, response, context對(duì)象
如果閱讀koa文檔,會(huì)發(fā)現(xiàn)koa有三個(gè)重要的對(duì)象,分別是request, response, context。其中request是對(duì)node原生的request的封裝,response是對(duì)node原生response對(duì)象的封裝,context對(duì)象則是回調(diào)函數(shù)上下文對(duì)象,掛載了koa request和response對(duì)象。下面我們一一來說明。
首先要明確的是,對(duì)于koa的request和response對(duì)象,只是提供了對(duì)node原生request和response對(duì)象的一些方法的封裝,明確了這一點(diǎn),我們的思路是,使用js的getter和setter屬性,基于node的對(duì)象req/res對(duì)象封裝koa的request/response對(duì)象。
規(guī)劃一下我們要封裝哪些易用的方法。這里在文章中為了易懂,姑且只實(shí)現(xiàn)以下方法:
對(duì)于simpleKoa request對(duì)象,實(shí)現(xiàn)query讀取方法,能夠讀取到url中的參數(shù),返回一個(gè)對(duì)象。
對(duì)于simpleKoa response對(duì)象,實(shí)現(xiàn)status讀寫方法,分別是讀取和設(shè)置http response的狀態(tài)碼,以及body方法,用于構(gòu)造返回信息。
而simpleKoa context對(duì)象,則掛載了request和response對(duì)象,并對(duì)一些常用方法進(jìn)行了代理。
首先創(chuàng)建request.js:
// request.js let url = require('url'); module.exports = { get query() { return url.parse(this.req.url, true).query; } };
很簡(jiǎn)單,就是導(dǎo)出了一個(gè)對(duì)象,其中包含了一個(gè)query的讀取方法,通過url.parse方法解析url中的參數(shù),并以對(duì)象的形式返回。需要注意的是,代碼中的this.req代表的是node的原生request對(duì)象,this.req.url就是node原生request中獲取url的方法。稍后我們修改application.js的時(shí)候,會(huì)為koa的request對(duì)象掛載這個(gè)req。
然后創(chuàng)建response.js:
// response.js module.exports = { get body() { return this._body; }, /** * 設(shè)置返回給客戶端的body內(nèi)容 * * @param {mixed} data body內(nèi)容 */ set body(data) { this._body = data; }, get status() { return this.res.statusCode; }, /** * 設(shè)置返回給客戶端的stausCode * * @param {number} statusCode 狀態(tài)碼 */ set status(statusCode) { if (typeof statusCode !== 'number') { throw new Error('statusCode must be a number!'); } this.res.statusCode = statusCode; } };
也很簡(jiǎn)單。status讀寫方法分別設(shè)置或讀取this.res.statusCode。同樣的,這個(gè)this.res是掛載的node原生response對(duì)象。而body讀寫方法分別設(shè)置、讀取一個(gè)名為this._body的屬性。這里設(shè)置body的時(shí)候并沒有直接調(diào)用this.res.end來返回信息,這是考慮到koa當(dāng)中我們可能會(huì)多次調(diào)用response的body方法覆蓋性設(shè)置數(shù)據(jù)。真正的返回消息操作會(huì)在application.js中存在。
然后我們創(chuàng)建context.js文件,構(gòu)造context對(duì)象的原型:
// context.js module.exports = { get query() { return this.request.query; }, get body() { return this.response.body; }, set body(data) { this.response.body = data; }, get status() { return this.response.status; }, set status(statusCode) { this.response.status = statusCode; } };
可以看到主要是做一些常用方法的代理,通過context.query直接代理了context.request.query,context.body和context.status代理了context.response.body與context.response.status。而context.request,context.response則會(huì)在application.js中掛載。
由于context對(duì)象定義比較簡(jiǎn)單并且規(guī)范,當(dāng)實(shí)現(xiàn)更多代理方法時(shí)候,這樣一個(gè)一個(gè)通過聲明的方式顯然有點(diǎn)笨,js中,設(shè)置setter/getter,可以通過對(duì)象的__defineSetter__和__defineSetter__來實(shí)現(xiàn)。為此,我們精簡(jiǎn)了上面的context.js實(shí)現(xiàn)方法,精簡(jiǎn)版本如下:
let proto = {}; // 為proto名為property的屬性設(shè)置setter function delegateSet(property, name) { proto.__defineSetter__(name, function (val) { this[property][name] = val; }); } // 為proto名為property的屬性設(shè)置getter function delegateGet(property, name) { proto.__defineGetter__(name, function () { return this[property][name]; }); } // 定義request中要代理的setter和getter let requestSet = []; let requestGet = ['query']; // 定義response中要代理的setter和getter let responseSet = ['body', 'status']; let responseGet = responseSet; requestSet.forEach(ele => { delegateSet('request', ele); }); requestGet.forEach(ele => { delegateGet('request', ele); }); responseSet.forEach(ele => { delegateSet('response', ele); }); responseGet.forEach(ele => { delegateGet('response', ele); }); module.exports = proto;
這樣,當(dāng)我們希望代理更多request和response方法的時(shí)候,可以直接向requestGet/requestSet/responseGet/responseSet數(shù)組中添加method的名稱即可(前提是在request和response中實(shí)現(xiàn)了)。
最后讓我們來修改application.js,基于剛才的3個(gè)對(duì)象原型來創(chuàng)建request, response, context對(duì)象:
// application.js let http = require('http'); let context = require('./context'); let request = require('./request'); let response = require('./response'); class Application { /** * 構(gòu)造函數(shù) */ constructor() { this.callbackFunc; this.context = context; this.request = request; this.response = response; } /** * 開啟http server并傳入callback */ listen(...args) { let server = http.createServer(this.callback()); server.listen(...args); } /** * 掛載回調(diào)函數(shù) * @param {Function} fn 回調(diào)處理函數(shù) */ use(fn) { this.callbackFunc = fn; } /** * 獲取http server所需的callback函數(shù) * @return {Function} fn */ callback() { return (req, res) => { let ctx = this.createContext(req, res); let respond = () => this.responseBody(ctx); this.callbackFunc(ctx).then(respond); }; } /** * 構(gòu)造ctx * @param {Object} req node req實(shí)例 * @param {Object} res node res實(shí)例 * @return {Object} ctx實(shí)例 */ createContext(req, res) { // 針對(duì)每個(gè)請(qǐng)求,都要?jiǎng)?chuàng)建ctx對(duì)象 let ctx = Object.create(this.context); ctx.request = Object.create(this.request); ctx.response = Object.create(this.response); ctx.req = ctx.request.req = req; ctx.res = ctx.response.res = res; return ctx; } /** * 對(duì)客戶端消息進(jìn)行回復(fù) * @param {Object} ctx ctx實(shí)例 */ responseBody(ctx) { let content = ctx.body; if (typeof content === 'string') { ctx.res.end(content); } else if (typeof content === 'object') { ctx.res.end(JSON.stringify(content)); } } }
可以看到,最主要的是增加了createContext方法,基于我們之前創(chuàng)建的context 為原型,使用Object.create(this.context)方法創(chuàng)建了ctx,并同樣通過Object.create(this.request)和Object.create(this.response)創(chuàng)建了request/response對(duì)象并掛在到了ctx對(duì)象上面。此外,還將原生node的req/res對(duì)象掛載到了ctx.request.req/ctx.req和ctx.response.res/ctx.res對(duì)象上。
回過頭去看我們之前的context/request/response.js文件,就能知道當(dāng)時(shí)使用的this.res或者this.response之類的是從哪里來的了,原來是在這個(gè)createContext方法中掛載到了對(duì)應(yīng)的實(shí)例上。一張圖來說明其中的關(guān)系:
構(gòu)建了運(yùn)行時(shí)上下文ctx之后,我們的app.use回調(diào)函數(shù)參數(shù)就都基于ctx了。
下面一張圖描述了ctx對(duì)象的結(jié)構(gòu)和繼承關(guān)系:
最后回憶我們的ctx.body方法,并沒有直接返回消息體,而是將消息存儲(chǔ)在了一個(gè)變量屬性中。為了每次回調(diào)函數(shù)處理結(jié)束之后返回消息,我們創(chuàng)建了responseBody方法,主要作用就是通過ctx.body讀取存儲(chǔ)的消息,然后調(diào)用ctx.res.end返回消息并關(guān)閉連接。從方法中知道,我們的body消息體可以是字符串,也可以是對(duì)象(會(huì)序列化為字符串返回)。注意這個(gè)方法的調(diào)用是在回調(diào)函數(shù)結(jié)束之后調(diào)用的,而我們的回調(diào)函數(shù)是一個(gè)async函數(shù),其執(zhí)行結(jié)束后會(huì)返回一個(gè)Promise對(duì)象,因此我們只需要在其后通過.then方法調(diào)用我們的responseBody即可,這就是this.callbackFunc(ctx).then(respond)的意義。
然后我們來測(cè)試一下目前為止的框架。修改example.js如下:
let simpleKoa = require('./application'); let app = new simpleKoa(); app.use(async ctx => { ctx.body = 'hello ' + ctx.query.name; }); app.listen(3000, () => { console.log('listening on 3000'); });
可以看到這個(gè)時(shí)候我們通過app.use傳入的已經(jīng)不再是原生的function (req, res)回調(diào)函數(shù),而是koa2中的async函數(shù),接收ctx作為參數(shù)。為了測(cè)試,在瀏覽器訪問localhost:3000?name=tom,可以看到返回了'hello tom',符合預(yù)期。
這里再插入分析一個(gè)知識(shí)概念。從剛才的實(shí)現(xiàn)中,我們知道了this.context是我們的中間件中上下文ctx對(duì)象的原型。因此在實(shí)際開發(fā)中,我們可以將一些常用的方法掛載到this.context上面,這樣,在中間件ctx中,我們也可以方便的使用這些方法了,這個(gè)概念就叫做ctx的擴(kuò)展,一個(gè)例子是阿里的egg.js框架已經(jīng)把這個(gè)擴(kuò)展機(jī)制作為一部分,融入到了框架開發(fā)中。
下面就展示一個(gè)例子,我們寫一個(gè)echoData的方法作為擴(kuò)展,傳入errno, data, errmsg,能夠給客戶端返回結(jié)構(gòu)化的消息結(jié)果:
let SimpleKoa = require('./application'); let app = new SimpleKoa(); // 對(duì)ctx進(jìn)行擴(kuò)展 app.context.echoData = function (errno = 0, data = null, errmsg = '') { this.res.setHeader('Content-Type', 'application/json;charset=utf-8'); this.body = { errno: errno, data: data, errmsg: errmsg }; }; app.use(async ctx => { let data = { name: 'tom', age: 16, sex: 'male' } // 這里使用擴(kuò)展,方便的返回utf-8格式編碼,帶有errno和errmsg的消息體 ctx.echoData(0, data, 'success'); }); app.listen(3000, () => { console.log('listenning on 3000'); });
主線三:中間件機(jī)制
到目前為止,我們成功封裝了http server,并構(gòu)造了context, request, response對(duì)象。但最重要的一條主線卻還沒有實(shí)現(xiàn),那就是koa的中間件機(jī)制。
關(guān)于koa的中間件洋蔥執(zhí)行模型,koa 1中使用的是generator + co.js執(zhí)行的方式,koa 2中則使用了async/await。關(guān)于koa 1中的中間件原理,我曾寫過一篇文章進(jìn)行解釋,請(qǐng)移步:深入探析koa之中間件流程控制篇
這里我們實(shí)現(xiàn)的是基于koa 2的,因此再描述一下原理。為了便于理解,假設(shè)我們有3個(gè)async函數(shù):
async function m1(next) { console.log('m1'); await next(); } async function m2(next) { console.log('m2'); await next(); } async function m3() { console.log('m3'); }
我們希望能夠構(gòu)造出一個(gè)函數(shù),實(shí)現(xiàn)的效果是讓三個(gè)函數(shù)依次執(zhí)行。首先考慮想讓m2執(zhí)行完畢后,await next()去執(zhí)行m3函數(shù),那么顯然,需要構(gòu)造一個(gè)next函數(shù),作用是調(diào)用m3,然后作為參數(shù)傳給m2
let next1 = async function () { await m3(); } m2(next1); // 輸出:m2,m3
進(jìn)一步,考慮從m1開始執(zhí)行,那么,m1的next參數(shù)需要是一個(gè)執(zhí)行m2的函數(shù),并且給m2傳入的參數(shù)是m3,下面來模擬:
let next1 = async function () { await m3(); } let next2 = async function () { await m2(next1); } m1(next2); // 輸出:m1,m2,m3
那么對(duì)于n個(gè)async函數(shù),希望他們按順序依次執(zhí)行呢?可以看到,產(chǎn)生nextn的過程能夠抽象為一個(gè)函數(shù):
function createNext(middleware, oldNext) { return async function () { await middleware(oldNext); } } let next1 = createNext(m3, null); let next2 = createNext(m2, next1); let next3 = createNext(m1, next2); next3(); // 輸出m1, m2, m3
進(jìn)一步精簡(jiǎn):
let middlewares = [m1, m2, m3]; let len = middlewares.length; // 最后一個(gè)中間件的next設(shè)置為一個(gè)立即resolve的promise函數(shù) let next = async function () { return Promise.resolve(); } for (let i = len - 1; i >= 0; i--) { next = createNext(middlewares[i], next); } next(); // 輸出m1, m2, m3
至此,我們也有了koa中間件機(jī)制實(shí)現(xiàn)的思路,新的application.js如下:
/** * @file simpleKoa application對(duì)象 */ let http = require('http'); let context = require('./context'); let request = require('./request'); let response = require('.//response'); class Application { /** * 構(gòu)造函數(shù) */ constructor() { this.middlewares = []; this.context = context; this.request = request; this.response = response; } // ...省略中間 /** * 中間件掛載 * @param {Function} middleware 中間件函數(shù) */ use(middleware) { this.middlewares.push(middleware); } /** * 中間件合并方法,將中間件數(shù)組合并為一個(gè)中間件 * @return {Function} */ compose() { // 將middlewares合并為一個(gè)函數(shù),該函數(shù)接收一個(gè)ctx對(duì)象 return async ctx => { function createNext(middleware, oldNext) { return async () => { await middleware(ctx, oldNext); } } let len = this.middlewares.length; let next = async () => { return Promise.resolve(); }; for (let i = len - 1; i >= 0; i--) { let currentMiddleware = this.middlewares[i]; next = createNext(currentMiddleware, next); } await next(); }; } /** * 獲取http server所需的callback函數(shù) * @return {Function} fn */ callback() { return (req, res) => { let ctx = this.createContext(req, res); let respond = () => this.responseBody(ctx); let fn = this.compose(); return fn(ctx).then(respond); }; } // ...省略后面 } module.exports = Application;
可以看到,首先對(duì)app.use進(jìn)行改造了,每次調(diào)用app.use,就向this.middlewares中push一個(gè)回調(diào)函數(shù)。然后增加了一個(gè)compose()方法,利用我們前文分析的原理,對(duì)middlewares數(shù)組中的函數(shù)進(jìn)行組裝,返回一個(gè)最終的函數(shù)。最后,在callback()方法中,調(diào)用compose()得到最終回調(diào)函數(shù),并執(zhí)行。
改寫example.js驗(yàn)證一下中間件機(jī)制:
let simpleKoa = require('./application'); let app = new simpleKoa(); let responseData = {}; app.use(async (ctx, next) => { responseData.name = 'tom'; await next(); ctx.body = responseData; }); app.use(async (ctx, next) => { responseData.age = 16; await next(); }); app.use(async ctx => { responseData.sex = 'male'; }); app.listen(3000, () => { console.log('listening on 3000'); }); // 返回{ name: "tom", age: 16, sex: "male"}
例子中一共三個(gè)中間件,分別對(duì)responseData增加了name, age, sex屬性,最后返回該數(shù)據(jù)。
至此,一個(gè)koa框架基本已經(jīng)浮出水面了,不過我們還需要進(jìn)行最后一個(gè)主線的分析:錯(cuò)誤處理。
主線四:錯(cuò)誤處理
一個(gè)健壯的框架,必須保證在發(fā)生錯(cuò)誤的時(shí)候,能夠捕獲錯(cuò)誤并有降級(jí)方案返回給客戶端。但顯然現(xiàn)在我們的框架還做不到這一點(diǎn),假設(shè)我們修改一下例子,我們的中間件中,有一個(gè)發(fā)生錯(cuò)誤拋出了異常:
let simpleKoa = require('./application'); let app = new simpleKoa(); let responseData = {}; app.use(async (ctx, next) => { responseData.name = 'tom'; await next(); ctx.body = responseData; }); app.use(async (ctx, next) => { responseData.age = 16; await next(); }); app.use(async ctx => { responseData.sex = 'male'; // 這里發(fā)生了錯(cuò)誤,拋出了異常 throw new Error('oooops'); }); app.listen(3000, () => { console.log('listening on 3000'); });
這個(gè)時(shí)候訪問瀏覽器,是得不到任何響應(yīng)的,這是因?yàn)楫惓2]有被我們的框架捕獲并進(jìn)行降級(jí)處理?;仡櫸覀僡pplication.js中的中間件執(zhí)行代碼:
// application.js // ... callback() { return (req, res) => { let ctx = this.createContext(req, res); let respond = () => this.responseBody(ctx); let fn = this.compose(); return fn(ctx).then(respond); }; } // ...
其中我們知道,fn是一個(gè)async函數(shù),執(zhí)行后返回一個(gè)promise,回想promise的錯(cuò)誤處理是怎樣的?沒錯(cuò),我們只需要定義一個(gè)onerror函數(shù),里面進(jìn)行錯(cuò)誤發(fā)生時(shí)候的降級(jí)處理,然后在promise的catch方法中引用這個(gè)函數(shù)即可。
于此同時(shí),回顧koa框架,我們知道在錯(cuò)誤發(fā)生的時(shí)候,app對(duì)象可以通過app.on('error', callback)訂閱錯(cuò)誤事件,這有助于我們幾種處理錯(cuò)誤,比如打印日志之類的操作。為此,我們也要對(duì)Application對(duì)象進(jìn)行改造,讓其繼承nodejs中的events對(duì)象,然后在onerror方法中emit錯(cuò)誤事件。改造后的application.js如下:
/** * @file simpleKoa application對(duì)象 */ let EventEmitter = require('events'); let http = require('http'); let context = require('./context'); let request = require('./request'); let response = require('./response'); class Application extends EventEmitter { /** * 構(gòu)造函數(shù) */ constructor() { super(); this.middlewares = []; this.context = context; this.request = request; this.response = response; } // ... /** * 獲取http server所需的callback函數(shù) * @return {Function} fn */ callback() { return (req, res) => { let ctx = this.createContext(req, res); let respond = () => this.responseBody(ctx); let onerror = (err) => this.onerror(err, ctx); let fn = this.compose(); // 在這里catch異常,調(diào)用onerror方法處理異常 return fn(ctx).then(respond).catch(onerror); }; } // ... /** * 錯(cuò)誤處理 * @param {Object} err Error對(duì)象 * @param {Object} ctx ctx實(shí)例 */ onerror(err, ctx) { if (err.code === 'ENOENT') { ctx.status = 404; } else { ctx.status = 500; } let msg = err.message || 'Internal error'; ctx.res.end(msg); // 觸發(fā)error事件 this.emit('error', err); } } module.exports = Application;
可以看到,onerror方法的對(duì)異常的處理主要是獲取異常狀態(tài)碼,當(dāng)err.code為'ENOENT'的時(shí)候,返回的消息頭設(shè)置為404,否則默認(rèn)設(shè)置為500,然后消息體設(shè)置為err.message,如果異常中message屬性為空,則默認(rèn)消息體設(shè)置為'Internal error'。此后調(diào)用ctx.res.end返回消息,這樣就能保證即使異常情況下,客戶端也能收到返回值。最后通過this.emit出發(fā)error事件。
然后我們寫一個(gè)example來驗(yàn)證錯(cuò)誤處理:
let simpleKoa = require('./application'); let app = new simpleKoa(); app.use(async ctx => { throw new Error('ooops'); }); app.on('error', (err) => { console.log(err.stack); }); app.listen(3000, () => { console.log('listening on 3000'); });
瀏覽器訪問'localhost:3000'的時(shí)候,得到返回'ooops',同時(shí)http狀態(tài)碼為500 。同時(shí)app.on('error')訂閱到了異常事件,在回調(diào)函數(shù)中打印出了錯(cuò)誤棧信息。
關(guān)于錯(cuò)誤處理,這里多說一點(diǎn)。雖然koa中內(nèi)置了錯(cuò)誤處理機(jī)制,但是實(shí)際業(yè)務(wù)開發(fā)中,我們往往希望能夠自定義錯(cuò)誤處理方式,這個(gè)時(shí)候,比較好的辦法是在最開頭增加一個(gè)錯(cuò)誤捕獲中間件,然后根據(jù)錯(cuò)誤進(jìn)行定制化的處理,比如:
// 錯(cuò)誤處理中間件 app.use(async (ctx, next) => { try { await next(); } catch (err) { // 在這里進(jìn)行定制化的錯(cuò)誤處理 } }); // ...其他中間件
至此,我們就完整實(shí)現(xiàn)了一個(gè)輕量版的koa框架。
結(jié)語
完整的simpleKoa代碼庫地址為:simpleKoa,里面還附帶了一些example。
理解了這個(gè)輕量版koa的實(shí)現(xiàn)原理,讀者還可以去看看koa的源碼,會(huì)發(fā)現(xiàn)機(jī)制和我們實(shí)現(xiàn)的框架是非常類似的,無非是多了一些細(xì)節(jié),比如說,完整koa的context/request/response方法上面掛載了更多好用的method,或者很多方法中容錯(cuò)處理更好等等。具體在本文中就不展開講了,留給感興趣的讀者去探索吧~。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。