閱讀目錄html
|-- lib | |--- application.js | |--- context.js | |--- request.js | |--- response.js |__ package.json
application.js 是Koa2的入口文件,它封裝了 context, request, response, 及 中間件處理的流程, 及 它向外導出了class的實列,而且它繼承了Event, 所以該框架支持事件監聽和觸發的能力,好比代碼: module.exports = class Application extends Emitter {}.node
context.js 是處理應用的上下文ctx。它封裝了 request.js 和 response.js 的方法。
request.js 它封裝了處理http的請求。
response.js 它封裝了處理http響應。git
所以實現koa2框架須要封裝和實現以下四個模塊:github
1. 封裝node http server. 建立koa類構造函數。
2. 構造request、response、及 context 對象。
3. 中間件機制的實現。
4. 錯誤捕獲和錯誤處理。json
一:封裝node http server. 建立koa類構造函數api
首先,若是咱們使用node的原生模塊實現一個簡單的服務器,而且打印 hello world,代碼通常是以下所示:數組
const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200); res.end('hello world....'); }); server.listen(3000, () => { console.log('listening on 3000'); });
所以實現koa的第一步是,咱們須要對該原生模塊進行封裝一下,咱們首先要建立application.js實現一個Application對象。promise
基本代碼封裝成以下(假如咱們把代碼放到 application.js裏面):瀏覽器
const Emitter = require('events'); const http = require('http'); class Application extends Emitter { /* 構造函數 */ constructor() { super(); this.callbackFunc = null; } // 開啓 http server 而且傳入參數 callback listen(...args) { const server = http.createServer(this.callback()); return server.listen(...args); } use(fn) { this.callbackFunc = fn; } callback() { return (req, res) => { this.callbackFunc(req, res); } } } module.exports = Application;
而後咱們在該目錄下新建一個 test.js 文件,使用以下代碼進行初始化以下:服務器
const testKoa = require('./application'); const app = new testKoa(); app.use((req, res) => { res.writeHead(200); res.end('hello world....'); }); app.listen(3000, () => { console.log('listening on 3000'); });
如上基本代碼咱們能夠看到,在application.js 咱們簡單的封裝了一個 http server,使用app.use註冊回調函數,app.listen監聽server,並傳入回調函數。
可是如上代碼有個缺點,app.use 傳入的回調函數參數仍是req,res, 也就是node原生的request和response對象,使用該對象仍是不夠方便,它不符合框架的設計的易用性,咱們須要封裝成以下的樣子:
const Koa = require('koa'); const app = new Koa(); app.use(async (ctx, next) => { console.log(11111); await next(); console.log(22222); }); app.listen(3000, () => { console.log('listening on 3000'); });
基於以上的緣由,咱們須要構造 request, response, 及 context對象了。
二:構造request、response、及 context 對象。
2.1 request.js
該模塊的做用是對原生的http模塊的request對象進行封裝,對request對象的某些屬性或方法經過重寫 getter/setter函數進行代理。
所以咱們須要在咱們項目中根目錄下新建一個request.js, 該文件只有獲取和設置url的方法,最後導出該文件,代碼以下:
const request = { get url() { return this.req.url; }, set url(val) { this.req.url = val; } }; module.exports = request;
如須要理解get/set對對象的監聽能夠看我這篇文章
如上代碼很簡單,導出一個對象,該文件中包含了獲取和設置url的方法,代碼中this.req是node原生中的request對象,this.req.url則是node原生request中獲取url的方法。
2. response.js
response.js 也是對http模塊的response對象進行封裝,經過對response對象的某些屬性或方法經過getter/setter函數進行代理。
同理咱們須要在咱們項目的根目錄下新建一個response.js。基本代碼像以下所示:
const response = { get body() { return this._body; }, set body(data) { this._body = data; }, get status() { return this.res.statusCode; }, set status(statusCode) { if (typeof statusCode !== 'number') { throw new Error('statusCode 必須爲一個數字'); } this.res.statusCode = statusCode; } }; module.exports = response;
代碼也是如上一些簡單的代碼,該文件中有四個方法,分別是 body讀取和設置方法。讀取一個名爲 this._body 的屬性。
status方法分別是設置或讀取 this.res.statusCode。同理:this.res是node原生中的response對象。
3. context.js
如上是簡單的 request.js 和 response.js ,那麼context的核心是將 request, response對象上的屬性方法代理到context對象上。也就是說 將會把 this.res.statusCode 就會變成 this.ctx.statusCode 相似於這樣的代碼。request.js和response.js 中全部的方法和屬性都能在ctx對象上找到。
所以咱們須要在項目中的根目錄下新建 context.js, 基本代碼以下:
const context = { get url() { return this.request.url; }, set url(val) { this.request.url = val; }, get body() { return this.response.body; }, set body(data) { this.response.body = data; }, get status() { return this.response.statusCode; }, set status(statusCode) { if (typeof statusCode !== 'number') { throw new Error('statusCode 必須爲一個數字'); } this.response.statusCode = statusCode; } }; module.exports = context;
如上代碼能夠看到context.js 是作一些經常使用方法或屬性的代理,好比經過 context.url 直接代理了 context.request.url.
context.body 代理了 context.response.body, context.status 代理了 context.response.status. 可是 context.request、context.response會在application.js中掛載的。
注意:想要瞭解 getter/setter 的代理原理能夠看這篇文章.
如上是簡單的代理,可是當有不少代理的時候,咱們一個個編寫有點繁瑣,所以咱們能夠經過 __defineSetter__ 和 __defineGetter__來實現,該兩個方法目前不建議使用,咱們也能夠經過Object.defineProperty這個來監聽對象。
可是目前在koa2中仍是使用 delegates模塊中的 __defineSetter__ 和 __defineGetter來實現的。delegates模塊它的做用是將內部對象上的變量或函數委託到外部對象上。具體想要瞭解 delegates模塊 請看我這篇文章。
所以咱們的context.js 代碼能夠改爲以下(固然咱們須要引入delegates模塊中的代碼引入進來);
const delegates = require('./delegates'); const context = { // ..... 其餘不少代碼 }; // 代理request對象 delegates(context, 'request').access('url'); // 代理response對象 delegates(context, 'response').access('body').access('status'); /* const context = { get url() { return this.request.url; }, set url(val) { this.request.url = val; }, get body() { return this.response.body; }, set body(data) { this.response.body = data; }, get status() { return this.response.statusCode; }, set status(statusCode) { if (typeof statusCode !== 'number') { throw new Error('statusCode 必須爲一個數字'); } this.response.statusCode = statusCode; } }; */ module.exports = context;
如上代碼引入了 delegates.js 模塊,而後使用該模塊下的access的方法,該方法既擁有setter方法,也擁有getter方法,所以代理了request對象中的url方法,同時代理了context對象中的response屬性中的 body 和 status方法。
最後咱們須要來修改application.js代碼,引入request,response,context對象。以下代碼:
const Emitter = require('events'); const http = require('http'); // 引入 context request, response 模塊 const context = require('./context'); const request = require('./request'); const response = require('./response'); class Application extends Emitter { /* 構造函數 */ constructor() { super(); this.callbackFunc = null; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } // 開啓 http server 而且傳入參數 callback listen(...args) { const server = http.createServer(this.callback()); return server.listen(...args); } use(fn) { this.callbackFunc = fn; } callback() { return (req, res) => { // this.callbackFunc(req, res); // 建立ctx const ctx = this.createContext(req, res); // 響應內容 const response = () => this.responseBody(ctx); this.callbackFunc(ctx).then(response); } } /* 構造ctx @param {Object} req實列 @param {Object} res 實列 @return {Object} ctx實列 */ createContext(req, res) { // 每一個實列都要建立一個ctx對象 const ctx = Object.create(this.context); // 把request和response對象掛載到ctx上去 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; } /* 響應消息 @param {Object} ctx 實列 */ responseBody(ctx) { const content = ctx.body; if (typeof content === 'string') { ctx.res.end(content); } else if (typeof content === 'object') { ctx.res.end(JSON.stringify(content)); } } } module.exports = Application;
如上代碼能夠看到在callback()函數內部,咱們把以前的這句代碼 this.callbackFunc(req, res); 註釋掉了,改爲以下代碼:
// 建立ctx const ctx = this.createContext(req, res); // 響應內容 const response = () => this.responseBody(ctx); this.callbackFunc(ctx).then(response);
1. 首先是使用 createContext() 方法來建立ctx。而後把request對和response對象都直接掛載到了 ctx.request 和 ctx.response上,而且還將node原生的req/res對象掛載到了 ctx.request.req/ctx.req 和 ctx.response.res/ctx.res上了。
咱們再來看下 request.js 的代碼:
const request = { get url() { return this.req.url; }, set url(val) { this.req.url = val; } }; module.exports = request;
咱們以前request.js 代碼是如上寫的,好比 get url() 方法,返回的是 this.req.url, this.req是從什麼地方來的?以前咱們並不理解,如今咱們知道了。
1. 首先咱們把request掛載到ctx實列上了,如代碼:ctx.request = Object.create(this.request);而後node中的原生的req也掛載到ctx.req中了,如代碼:ctx.req = ctx.request.req = req; 所以request.js 中的this指向了createContext方法中掛載到了對應的實例上。所以 this.req.url 實際上就是 ctx.req.url了。同理 this.res 也是同樣的道理的。
2. 其次,咱們使用 const response = () => this.responseBody(ctx); 該方法把ctx實列做用參數傳入 responseBody方法內做爲
響應內容。代碼以下:
responseBody(ctx) { const content = ctx.body; if (typeof content === 'string') { ctx.res.end(content); } else if (typeof content === 'object') { ctx.res.end(JSON.stringify(content)); } }
如上咱們建立了 responseBody方法,該方法的做用是經過ctx.body讀取信息,判斷該 ctx.body是不是字符串或對象,若是是對象的話,也會把它轉爲字符串,最後調用 ctx.res.end() 方法返回信息並關閉鏈接。
3. 最後咱們調用該代碼:this.callbackFunc(ctx).then(response); this.callbackFunc()函數就是咱們使用koa中傳入的方法,好比以下koa代碼:
app.use(async ctx => { console.log(ctx.status); // 打印狀態碼爲200 ctx.body = 'hello world'; });
該回調函數是一個async函數,而後返回給咱們的參數是ctx對象,async函數返回的是一個promise對象,所以在源碼中咱們繼續調用then方法,把返回的內容掛載到ctx上。所以咱們能夠拿着ctx對象作咱們本身想要作的事情了。
三:中間件機制的實現。
koa中的中間件是洋蔥型模型。具體的洋蔥模型的機制能夠看這篇文章。
koa2中使用了async/await做爲執行方式,具體理解 async/await的含義能夠看我這篇文章介紹。
koa2中的中間件demo以下:
const Koa = require('koa'); const app = new Koa(); app.use(async (ctx, next) => { console.log(11111); await next(); console.log(22222); }); app.use(async (ctx, next) => { console.log(33333); await next(); console.log(44444); }); app.use(async (ctx, next) => { console.log(55555); await next(); console.log(66666); }); app.listen(3001); console.log('app started at port 3000...'); // 執行結果爲 11111 33333 55555 66666 44444 22222
如上執行結果爲 11111 33333 55555 66666 44444 22222,koa2中的中間件模型爲洋蔥型模型,當對象請求過來的時候,會依次通過各個中間件進行處理,當碰到 await next()時候就會跳到下一個中間件,當中間件沒有 await next執行的時候,就會逆序執行前面的中間件剩餘的代碼,所以,先打印出 11111,而後碰到await next()函數,因此跳到下一個中間件去,就接着打印33333, 而後又碰到 await next(),所以又跳到下一個中間件,所以會打印55555, 打印完成後,繼續碰到 await next() 函數,可是後面就沒有中間件了,所以執行打印66666,而後逆序打印後面的數據了,先打印44444,執行完成後,就往上打印22222.
逆序若是咱們很差理解的話,咱們繼續來看下以下demo就能明白了。
function test1() { console.log(1) test2(); console.log(5) return Promise.resolve(); } function test2() { console.log(2) test3(); console.log(4) } function test3() { console.log(3) return; } test1();
如上代碼打印的順序分別爲 1, 2, 3, 4, 5; 上面的代碼就是和koa2中的中間件逆序順序是同樣的哦。能夠本身理解一下。
那麼如今咱們想要實現這麼一個相似koa2中間件的這麼一個機制,咱們該如何作呢?
咱們都知道koa2中是使用了async/await來作的,假如咱們如今有以下三個簡單的async函數:
// 假以下面是三個測試函數,想要實現 koa中的中間件機制 async function fun1(next) { console.log(1111); await next(); console.log('aaaaaa'); } async function fun2(next) { console.log(22222); await next(); console.log('bbbbb'); } async function fun3() { console.log(3333); }
如上三個簡單的函數,我如今想構造出一個函數,讓這三個函數依次執行,先執行fun1函數,打印1111,而後碰到 await next() 後,執行下一個函數 fun2, 打印22222, 再碰到 await next() 就執行fun3函數,打印3333,而後繼續打印 bbbbb, 再打印 aaaaa。
所以咱們須要從第一個函數入手,由於首先打印的是 11111, 所以咱們須要構造一個調用 fun1函數了。fun1函數的next參數須要能調用 fun2函數了,fun2函數中的next參數須要能調用到fun3函數了。所以代碼改爲以下:
// 假以下面是三個測試函數,想要實現 koa中的中間件機制 async function fun1(next) { console.log(1111); await next(); console.log('aaaaaa'); } async function fun2(next) { console.log(22222); await next(); console.log('bbbbb'); } async function fun3() { console.log(3333); } let next1 = async function () { await fun2(next2); } let next2 = async function() { await fun3(); } fun1(next1);
而後咱們執行一下,就能夠看到函數會依次執行,結果爲:1111,22222,3333,bbbbb, aaaaaa;
如上就可讓函數依次執行了,可是假如頁面有n箇中間件函數,咱們須要依次執行怎麼辦呢?所以咱們須要抽象成一個公用的函數出來,據koa2中application.js 源碼中,首先會把全部的中間件函數放入一個數組裏面去,好比源碼中這樣的:
this.middleware.push(fn); 所以咱們這邊首先也能夠把上面的三個函數放入數組裏面去,而後使用for循環依次循環調用便可:
以下代碼:
async function fun1(next) { console.log(1111); await next(); console.log('aaaaaa'); } async function fun2(next) { console.log(22222); await next(); console.log('bbbbb'); } async function fun3() { console.log(3333); } function compose(middleware, oldNext) { return async function() { await middleware(oldNext); } } const middlewares = [fun1, fun2, fun3]; // 最後一箇中間件返回一個promise對象 let next = async function() { return Promise.resolve(); }; for (let i = middlewares.length - 1; i >= 0; i--) { next = compose(middlewares[i], next); } next();
最後依次會打印 1111 22222 3333 bbbbb aaaaaa了。
如上代碼是怎麼執行的呢?首先咱們會使用一個數組 middlewares 保存全部的函數,就像和koa2中同樣使用 app.use 後,會傳入async函數進去,而後會依次經過 this.middlewares 把對應的函數保存到數組裏面去。而後咱們從數組末尾依次循環該數組最後把返回的值保存到 next 變量裏面去。如上代碼:
所以for循環第一次打印 middlewares[i], 返回的是 fun3函數,next傳進來的是 async function { return Promise.resolve()} 這樣的函數,最後返回該next,那麼此時該next保存的值就是:
next = async function() { await func3(async function(){ return Promise.resolve(); }); }
for 循環第二次的時候,返回的是 fun2函數,next傳進來的是 上一次返回的函數,最後返回next, 那麼此時next保存的值就是
next = async function() { await func2(async function() { await func3(async function(){ return Promise.resolve(); }); }); }
for循環第三次的時候,返回的是 fun1 函數,next傳進來的又是上一次返回的async函數,最後也返回next,那麼此時next的值就變爲:
next = async function(){ await fun1(async function() { await fun2(async function() { await fun3(async function(){ return Promise.resolve(); }); }); }); };
所以咱們下面調用 next() 函數的時候,會依次執行 fun1 函數,執行完成後,就會調用 fun2 函數,再執行完成後,接着調用fun3函數,依次類推..... 最後一個函數返回 Promise.resolve() 中Promise成功狀態。
若是上面的async 函數依次調用很差理解的話,咱們能夠繼續看以下demo;代碼以下:
async function fun1(next) { console.log(1111); await next(); console.log('aaaaaa'); } async function fun2(next) { console.log(22222); await next(); console.log('bbbbb'); } async function fun3() { console.log(3333); } const next = async function(){ await fun1(async function() { await fun2(async function() { await fun3(async function(){ return Promise.resolve(); }); }); }); }; next();
最後結果也會依次打印 1111, 22222, 3333, bbbbb, aaaaaa;
所以上面就是咱們的koa2中間件機制了。咱們如今把咱們總結的機制運用到咱們application.js中了。所以application.js代碼變成以下:
const Emitter = require('events'); const http = require('http'); // 引入 context request, response 模塊 const context = require('./context'); const request = require('./request'); const response = require('./response'); class Application extends Emitter { /* 構造函數 */ constructor() { super(); // this.callbackFunc = null; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); // 保存全部的中間件函數 this.middlewares = []; } // 開啓 http server 而且傳入參數 callback listen(...args) { const server = http.createServer(this.callback()); return server.listen(...args); } use(fn) { // this.callbackFunc = fn; // 把全部的中間件函數存放到數組裏面去 this.middlewares.push(fn); return this; } callback() { return (req, res) => { // this.callbackFunc(req, res); // 建立ctx const ctx = this.createContext(req, res); // 響應內容 const response = () => this.responseBody(ctx); //調用 compose 函數,把全部的函數合併 const fn = this.compose(); return fn(ctx).then(response); } } /* 構造ctx @param {Object} req實列 @param {Object} res 實列 @return {Object} ctx實列 */ createContext(req, res) { // 每一個實列都要建立一個ctx對象 const ctx = Object.create(this.context); // 把request和response對象掛載到ctx上去 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; } /* 響應消息 @param {Object} ctx 實列 */ responseBody(ctx) { const content = ctx.body; if (typeof content === 'string') { ctx.res.end(content); } else if (typeof content === 'object') { ctx.res.end(JSON.stringify(content)); } } /* 把傳進來的全部的中間件函數合併爲一箇中間件 @return {function} */ compose() { // 該函數接收一個參數 ctx return async ctx => { function nextCompose(middleware, oldNext) { return async function() { await middleware(ctx, oldNext); } } // 獲取中間件的長度 let len = this.middlewares.length; // 最後一箇中間件返回一個promise對象 let next = async function() { return Promise.resolve(); }; for (let i = len; i >= 0; i--) { next = nextCompose(this.middlewares[i], next); } await next(); }; } } module.exports = Application;
1. 如上代碼在構造函數內部 constructor 定義了一個變量 this.middlewares = []; 目的是保存app.use(fn)全部的中間件函數,
2. 而後咱們在use函數內部,不是把fn賦值,而是把fn放到一個數組裏面去,以下代碼:
use(fn) { // this.callbackFunc = fn; // 把全部的中間件函數存放到數組裏面去 this.middlewares.push(fn); return this; }
3. 最後把全部的中間件函數合併爲一箇中間件函數;以下compose函數的代碼以下:
compose() { // 該函數接收一個參數 ctx return async ctx => { function nextCompose(middleware, oldNext) { return async function() { await middleware(ctx, oldNext); } } // 獲取中間件的長度 let len = this.middlewares.length; // 最後一箇中間件返回一個promise對象 let next = async function() { return Promise.resolve(); }; for (let i = len; i >= 0; i--) { next = nextCompose(this.middlewares[i], next); } await next(); }; }
該compose函數代碼和咱們以前的demo代碼是同樣的。這裏就很少作解析哦。
4. 在callback函數內部改爲以下代碼:
callback() { return (req, res) => { /* // 建立ctx const ctx = this.createContext(req, res); // 響應內容 const response = () => this.responseBody(ctx); this.callbackFunc(ctx).then(response); */ // 建立ctx const ctx = this.createContext(req, res); // 響應內容 const response = () => this.responseBody(ctx); //調用 compose 函數,把全部的函數合併 const fn = this.compose(); return fn(ctx).then(response); } }
如上代碼和以前版本的代碼,最主要的區別是 最後兩句代碼,以前的是直接把fn函數傳入到 this.callbackFunc函數內。如今是使用 this.compose()函數調用,把全部的async的中間件函數合併成一箇中間件函數後,把返回的合併後的中間件函數fn再去調用,這樣就會依次調用和初始化各個中間件函數,具體的原理機制咱們上面的demo已經講過了,這裏就再也不多描述了。
最後咱們須要一個測試文件,來測試該代碼:以下在test.js 代碼以下:
const testKoa = require('./application'); const app = new testKoa(); const obj = {}; app.use(async (ctx, next) => { obj.name = 'kongzhi'; console.log(1111); await next(); console.log('aaaaa'); }); app.use(async (ctx, next) => { obj.age = 30; console.log(2222); await next(); console.log('bbbbb') }); app.use(async (ctx, next) => { console.log(3333); console.log(obj); }); app.listen(3001, () => { console.log('listening on 3001'); });
咱們運行下便可看到,在命令行中會依次打印以下所示:
如上是先打印1111,2222,3333,{'name': 'kongzhi', 'age': 30}, bbbbb, aaaaa.
所以如上就是koa2中的中間件機制了。
四:錯誤捕獲和錯誤處理。
一個很是不錯的框架,當異常的時候,都但願能捕獲到該異常,而且但願把該異常返回給客戶端,讓開發者知道異常的一些信息。
好比koa2中的異常狀況下,會報錯以下信息:demo以下:
const Koa = require('koa'); const app = new Koa(); app.use((ctx) => { str += 'hello world'; // 沒有聲明該變量, 因此直接拼接字符串會報錯 ctx.body = str; }); app.on('error', (err, ctx) => { // 捕獲異常記錄錯誤日誌 console.log(err); }); app.listen(3000, () => { console.log('listening on 3000'); });
如上代碼,因爲str是一個未定義的變量,所以和字符串拼接的時候會報錯,可是koa2中咱們可使用 app.on('error', (err, ctx) => {}) 這樣的error方法來進行監聽的。所以在命令行中會報以下錯誤提示:
所以咱們如今也是同樣,咱們須要有對於某個中間件發生錯誤的時候,咱們須要監聽error這個事件進行監聽。
所以咱們須要定義一個onerror函數,當發生錯誤的時候,咱們可使用Promise中的catch方法來捕獲該錯誤了。
所以咱們可讓咱們的Application繼承於Event這個對象,在koa2源碼中的application.js 中有 onerror函數,咱們把它複製到咱們的Application.js 中,代碼以下:
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(); }
而後在咱們咱們的callback()函數中最後一句代碼使用catch去捕獲這個異常便可:代碼以下:
callback() { return (req, res) => { /* // 建立ctx const ctx = this.createContext(req, res); // 響應內容 const response = () => this.responseBody(ctx); this.callbackFunc(ctx).then(response); */ // 建立ctx const ctx = this.createContext(req, res); // 響應內容 const response = () => this.responseBody(ctx); // 響應時 調用error函數 const onerror = (err) => this.onerror(err, ctx); //調用 compose 函數,把全部的函數合併 const fn = this.compose(); return fn(ctx).then(response).catch(onerror); } }
所以Application.js 全部代碼以下:
const Emitter = require('events'); const http = require('http'); // 引入 context request, response 模塊 const context = require('./context'); const request = require('./request'); const response = require('./response'); class Application extends Emitter { /* 構造函數 */ constructor() { super(); // this.callbackFunc = null; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); // 保存全部的中間件函數 this.middlewares = []; } // 開啓 http server 而且傳入參數 callback listen(...args) { const server = http.createServer(this.callback()); return server.listen(...args); } use(fn) { // this.callbackFunc = fn; // 把全部的中間件函數存放到數組裏面去 this.middlewares.push(fn); return this; } callback() { return (req, res) => { /* // 建立ctx const ctx = this.createContext(req, res); // 響應內容 const response = () => this.responseBody(ctx); this.callbackFunc(ctx).then(response); */ // 建立ctx const ctx = this.createContext(req, res); // 響應內容 const response = () => this.responseBody(ctx); // 響應時 調用error函數 const onerror = (err) => this.onerror(err, ctx); //調用 compose 函數,把全部的函數合併 const fn = this.compose(); return fn(ctx).then(response).catch(onerror); } } /** * Default error handler. * * @param {Error} err * @api private */ 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(); } /* 構造ctx @param {Object} req實列 @param {Object} res 實列 @return {Object} ctx實列 */ createContext(req, res) { // 每一個實列都要建立一個ctx對象 const ctx = Object.create(this.context); // 把request和response對象掛載到ctx上去 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; } /* 響應消息 @param {Object} ctx 實列 */ responseBody(ctx) { const content = ctx.body; if (typeof content === 'string') { ctx.res.end(content); } else if (typeof content === 'object') { ctx.res.end(JSON.stringify(content)); } } /* 把傳進來的全部的中間件函數合併爲一箇中間件 @return {function} */ compose() { // 該函數接收一個參數 ctx return async ctx => { function nextCompose(middleware, oldNext) { return async function() { await middleware(ctx, oldNext); } } // 獲取中間件的長度 let len = this.middlewares.length; // 最後一箇中間件返回一個promise對象 let next = async function() { return Promise.resolve(); }; for (let i = len; i >= 0; i--) { next = nextCompose(this.middlewares[i], next); } await next(); }; } } module.exports = Application;
而後咱們使用test.js 編寫測試代碼以下:
const testKoa = require('./application'); const app = new testKoa(); app.use((ctx) => { str += 'hello world'; // 沒有聲明該變量, 因此直接拼接字符串會報錯 ctx.body = str; }); app.on('error', (err, ctx) => { // 捕獲異常記錄錯誤日誌 console.log(err); }); app.listen(3000, () => { console.log('listening on 3000'); });
當咱們在瀏覽器訪問的 http://localhost:3000/ 的時候,咱們能夠在命令行中看到以下報錯信息了:
總結:如上就是實現一個簡單的koa2框架的基本原理,原本想把koa2源碼也分析下,可是篇幅有限,因此下篇文章繼續把koa2全部的源碼簡單的解讀下,其實看懂這篇文章後,已經能夠理解95%左右的koa2源碼了,只是說koa2源碼中,好比request.js 會包含更多的方法,及 response.js 包含更多有用的方法等。