下面是application.js代码结构,并不是完整的源码,方便读者阅读node
class Application extends Emitter{
<!--初始化 new Koa()-->
constructor() {
this.proxy //若是为 true,则解析 "Host" 的 header 域,并支持 X-Forwarded-Host
this.subdomainOffset //表示 .subdomains 所忽略的字符偏移量
this.env // 默认为 NODE_ENV or "development"
this.middleware = [] // 中间件集合
this.context
this.request
this.response
this[util.inspect.custom] = this.inspect //将对象转换为字符串的方法,一般用于调试和错误输出,这里的用到node中util,this inspect调用 toJson方法中的only方法,返回对象
}
<!--listen方法,启动服务-->
listen(...args) {
<!--实际调用node.js 中的http.createServer服务-->
const server = http.createServer(this.callback()) //返回http.server实例
return server.listen(...args)
<!--这里面的args是端口号+回调函数,因此根据node官网api规定用于TCP链接-->
}
<!--调用中间件的use方法-->
use(fn){
<!--第一件事判断是不是generator函数,若是是利用koa-conver(实际这里面利用co插件的wrap方法)转化成async await 函数-->
if (isGeneratorFunction(fn)) {
fn = convert(fn)
}
<!--第二件事,中间件存储到定义的变量数组中-->
this.middleware.push(fn)
}
<!--http.createServer(callback)-->
callback() {
<!--第一步,利用koa-compose,整合全部的中间件-->
const fn = compose(this.middleware)
<!--闭包的形式返回-->
const handleRequest = (req, res) => {
<!--这里面req是http中IncomingMessage ,res是ServerResponse 类-->
<!--createContext方法,下面解析-->
const ctx = this.createContext(req, res)
<!---->
return this.handleRequest(ctx, fn)
}
return handleRequest
}
}
复制代码
<!--做为调试源码的程序-->
const Koa = require("Koa")
const app = new Koa()
app.use(async(ctx, next) => {
console.log(1)
await next()
console.log(2)
ctx.body = "这是第一个中间件"
})
app.use(async(ctx, next) => {
console.log(3)
await next()
console.log(4)
ctx.body = "这是第二个中间件"
})
app.listen(4001, () => { console.log("koa server is starting") })
复制代码
koa源码由application.js|context.js|request.js|response.js 文件组成,实际在application.js就引用了其余三个js文件,并引用koa-compose核心文件,下面是本人经过vscode断点调试跟踪上面koa程序的流程图,不过仍是但愿想深刻学习的人,自行断点调试,如图mysql
注意:实心箭头表明是请求后代码的流程,线性箭头是初始化服务加载的流程web
createContext(req, res) {
<!--this.context 引用个是require("./context"),下面的1.1章节-->
const context = Object.create(this.context)
<!--request、response都是先引用对应库的基础方法,而后从新建立个新的对象-->
const request = context.request = Object.create(this.request)
const response = context.response = Object.create(this.response)
<!--context对象包含request、response两个对象-->
context.app = request.app = response.app = this
context.req = request.req = response.req = req
context.res = request.res = response.res = res
<!--request、response两个对象又包含context-->
request.ctx = response.ctx = context
request.response = response
response.request = request
context.originalUrl = request.originalUrl = req.url
context.state = {}
return context
}
复制代码
const delegate = require('delegates')
const proto = module.exports = {
<!--有个跟上述application.js文件中inspect相似的方法,省略-->
<!--set cookie 和 get cookie 方法-->
}
delegate(proto, 'response')
.method('set')
...
.access('body')
...
delegate(proto, 'request')
.method('get')
...
.access('querystring')
...
<!--根据npm官网对delegates的解释就是委托事件,那么context.js重点的做用是res,req两个方法能加载这些委托的方法,换言之给context.js 返回的对象proto 对于request 、response 属性增长增、读取、设置、改变等基础操做方法-->
复制代码
利用上述的koa的启动程序,查看createContext的返回结果,也就是context对象的方法属性sql
{
"request": {
"method": "GET",
"url": "/favicon.ico",
"header": {
"host": "localhost:4001",
"connection": "keep-alive",
"pragma": "no-cache",
"cache-control": "no-cache",
"sec-fetch-mode": "no-cors",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36",
"accept": "image/webp,image/apng,image/*,*/*;q=0.8",
"sec-fetch-site": "same-origin",
"referer": "http://localhost:4001/",
"accept-encoding": "gzip, deflate, br",
"accept-language": "zh-CN,zh;q=0.9"
}
},
"response": {
"status": 200,
"message": "OK",
"header": {}
},
"app": {
"subdomainOffset": 2,
"proxy": false,
"env": "development"
},
"originalUrl": "/favicon.ico",
"req": "<original node req>",
"res": "<original node res>",
"socket": "<original node socket>"
}
复制代码
www.jianshu.com/p/bca3a00f9… 这位博主说的挺详细的,可是我打印出来的结果和上述“request、response是相互包含的关系,两个对象又包含context”这句话相矛盾,若是你经过打断点看,context和request、response是相互包含的关系npm
下面是koa-compose源码主要部分api
function compose (middleware) {
<!--判断是不是数组和函数,此处省略-->
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
<!--判断 i<==index,若是 true 的话,则说明 next() 方法调用屡次,若是你在同一个中间件中执行屡次next()方法,会出现下面的错误-->
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)
})) }
-->
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
复制代码
这里面最重要的函数是:数组
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
复制代码
一、上面代码执行了中间件fn(context, next),并传递了 context 和 next 函数两个参数,至于 next 函数则是返回一个 dispatch(i+1) 的执行结果;
二、这里面用bind方式,保证在app.use()方法中使用await next()手动执行下一个中间件,因此旧方法用function next () { return dispatch(i + 1) })这种方式看起来更直观;
三、最后(i+1)这个参数至关于执行了下一个中间件,从而造成递归调用。bash
阅读koa中的源码你会发现,它涉及最多的是闭包、高阶函数、函数柯里化,好比说callback方法和handleRequest()方法restful
callback() {
const fn = compose(this.middleware)
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
};
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
复制代码
一、callback方中handleRequest是一个闭包函数,由于它能访问callback函数的做用域变量fn;
二、callback以handleRequest函数做为返回值,而handleRequest又以this.handleRequest(ctx, fn)做为返回值,而且this.handleRequest(ctx, fn)参数是fn也是函数,显而易见这些函数都是高阶函数cookie
又好比说下面一段代码就是“函数柯里化”
<!--简化版的koa-compose,middleware参数名变成fn-->
<!--compose函数柯里化-->
function compose (middleware) {
return function (context, next) {
return dispatch(0)
function dispatch (i) {
<!--middleware参数名变成fn-->
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
}
}
}
<!--callback方法中调用,可是fn没有当即执行,是一个Promise对象-->
const fn = compose(this.middleware)
<!--handleRequest方法中fn名称变成fnMiddleware,此方法执行后,也就执行了fn方法,也就是中间件方法,才有中间件的回调,才有启动程序console.log的输出-->
handleRequest(ctx, fnMiddleware) {
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
复制代码
实现一个koa+mysql的简单项目,可是很规范的restful API