-
koa的核心源码只有4个文件
- application.js
- context.js
- request.js
- response.js
-
看下koa的基础用法,新建app.js
const Koa = require('koa')
const app = new Koa();
app.use(async (ctx, next) => {
console.log('111')
await next()
console.log('666')
})
app.use(async (ctx, next) => {
console.log('222')
await next()
console.log('555')
})
app.use(async (ctx, next) => {
console.log('333')
await next();
console.log('444')
})
let server = app.listen(3000, () => {
console.log('success in 3000')
})
- 依次输出 111 222 333 444 555 666
- 这就是koa的洋葱模型
-
分析application.js,这是入口文件
- 上面代码中使用了new Koa(),知道是个class
- 首先初始化
constructor(options) { super(); options = options || {}; this.proxy = options.proxy || false; // 是否代理 this.subdomainOffset = options.subdomainOffset || 2; // 子域名偏移位置 this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For'; this.maxIpsCount = options.maxIpsCount || 0; this.env = options.env || process.env.NODE_ENV || 'development'; // 开发环境 if (options.keys) this.keys = options.keys; this.middleware = []; // 中间件 this.context = Object.create(context); // 创建上下文 this.request = Object.create(request); // 创建请求 this.response = Object.create(response);// 创建响应 if (util.inspect.custom) { this[util.inspect.custom] = this.inspect; } }- 看下koa和node的listen用法
// node let app = http.createServer((req,res) => { res.end('123') }) app.listen(3003, () => { console.log('success in 3003') }) // koa app.listen(3000, () => { console.log('success in 3000') })- 看koa的listen用法,是在listen中传一个函数,这个函数包括node中的http.createServer和listen
- 在看下koa的listen源码
listen(...args) { debug('listen'); const server = http.createServer(this.callback()); return server.listen(...args); }- 从listen源码中看出果然是包括了node中的http.createServer和listen。
- 在原生node中http.createServer中是一个包含req,res的回调函数,在koa中,被封装到了this.callback函数中,下面看koa的callback函数
const compose = require('koa-compose'); callback() { const fn = compose(this.middleware); // 使用compose集合中间件,下面再分析compose if (!this.listenerCount('error')) this.on('error', this.onerror); const handleRequest = (req, res) => { const ctx = this.createContext(req, res); // 将req, res传入createContext,返回ctx上下文 return this.handleRequest(ctx, fn); // 将ctx(上下文),fn(中间件)传入handleRequest函数中 }; return handleRequest; }- koa-compse
- 可以看到返回一个方法,里面其实是递归执行,从dispatch(0)开始执行,然后到return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));不断递归,假设已经middleware.length最后一个,那么就返回一个Promise.resolve();,也就是这段代码实现了洋葱圈模型,也就是app.use里面的next参数实际上是执行之后的middleware,这里有个官方 gif 图讲述这个过程。
module.exports = compose function compose (middleware) { //传入的 middleware 参数必须是数组 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') //middleware 数组的元素必须是函数 for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } /** * @param {Object} context * @return {Promise} 返回一个闭包函数,函数的返回是一个Promise 对象, 保持对 middleware 的引用。 * @api public */ return 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, dispatch.bind(null, i + 1))); // 核心,假递归, } catch (err) { return Promise.reject(err) } } } }- 看下createContext做了什么
- 看下面代码,主要是将req,res等挂载到ctx上下文中,并返回ctx。
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.state = {}; return context; }- 看下handleRequest函数做了什么处理
handleRequest(ctx, fnMiddleware) { // 从callback函数传过来的两个参数ctx,fnMiddleware const res = ctx.res; // 响应内容 res.statusCode = 404; // 响应码 const onerror = err => ctx.onerror(err); //错误处理 const handleResponse = () => respond(ctx); // 正确的响应处理 onFinished(res, onerror); return fnMiddleware(ctx).then(handleResponse).catch(onerror); // 将ctx传入中间件后处理正确的响应处理和错误处理 }- koa通过use传入中间件,看下use函数
- 看出只是将中间件push到middleware中,并返回this,支持链式调用。
use(fn) { if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); if (isGeneratorFunction(fn)) { deprecate('Support for generators will be removed in v3. ' + 'See the documentation for examples of how to convert old middleware ' + 'https://github.com/koajs/koa/blob/master/docs/migration.md'); fn = convert(fn); } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); // 核心 return this; }