阅读目录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 包含更多有用的方法等。