本文介绍:
- koa如何处理请求的
- koa-compose函数
1.先看两幅图
这是大家常见的koa洋葱模型图
koa web服务器接受到一个api请求;需要经过一个个的处理函数(请求 -> |fn1| -> |fn2| -> |fn...| -> 响应)。如何把这一个个的处理函数串联起来依次执行。在koa中依靠的就是koa-compose这个函数。
如下图所示,一个请求进来需要经过权限校验、controller层、service层等等,经过各层函数(中间件的处理)之后才会将结果返回给接口调用方。
2.相关源码
先起一个koa服务,通过下面这个demo,配合源码看一下koa是如处理请求的。
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
console.log('---1--->')
await next()
console.log('===6===>')
})
app.use(async (ctx, next) => {
console.log('---2--->')
await next()
console.log('===5===>')
})
app.use(async (ctx, next) => {
console.log('---3--->')
await next()
console.log('===4===>')
})
app.listen(8899, () => {
console.log('应用启动了')
})
// 执行结构结果就是 1 2 3 4 5 6
通过源码来分析一下,koa是如何处理请求的。一下源码在 koa/lib/application.js目录下
1. app.listen()
listen(...args) {
const server = http.createServer(this.callback());
// 调用Node原生的http.createServer([requestListener])
// 参数requestListener是请求处理函数,用来响应request事件;
//此函数有两个参数req,res。当有请求进入的时候就会执行this.callback函数
return server.listen(...args); // port host等信息
}
2. 请求处理函数 this.callback()
callback() {
// 将中间件函数转化为compose函数
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
// request事件的响应函数,就是callback函数
const handleRequest = (req, res) => {
// 根据req, res创建 上下文
const ctx = this.createContext(req, res);
// 最终会调用handleRequest方法,将上下文以及compose函数传入
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
3. 中间件函数数组 this.middleware
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
// 在调用use的时候,把函数加到middleware数组中
this.middleware.push(fn);
return this;
}
4. callback函数实体 this.handleRequest
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err); // 对错误进行处理
const handleResponse = () => respond(ctx); // 对返回的结果进行处理
onFinished(res, onerror);
// 去依次执行compose函数,如何依次执行的?
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
整个过程的核心就是调用compose函数,将中间件函数数组转化为compose函数 const fn = compose(this.middleware);然后调用fn,就会依次执行所有的中间件函数;且上下文ctx会贯穿所有的中间件函数。
3.compose函数
首先看一下 koa-compose函数源码
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @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)
}
}
}
}
将一些注释容错判断等代码删除掉,可以发现koa-compose函数就十多行代码
function compose (middleware) {
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
index = i
let fn = middleware[i]
if (i === middleware.length) {
fn = next
}
if (!fn) return Promise.resolve()
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
}
}
}
通过以下4个简单的函数,看一下koa-compose函数使用效果
async function a (context,next) {
console.log('1.函数 【a】next之前-执行了.....')
await next()
console.log('8.函数 【a】next之后-执行了.....')
}
async function b (context, next) {
console.log('2.函数 【b】next之前-执行了.....')
await next()
console.log('7.函数 【b】next之后-执行了.....')
}
async function c (context, next) {
console.log('3.函数 【c】next之前-执行了.....')
await next()
console.log('6.函数 【c】next之后-执行了.....')
}
async function d (context, next) {
console.log('4.函数 【d】next之前-执行了.....')
console.log('5.函数 【d】next之后-执行了.....')
}
执行查看结果
var composeMiddles1 = compose([a,b,c,d])
composeMiddles()
// 执行结果
// 1.函数 【a】next之前-执行了.....
// 2.函数 【b】next之前-执行了.....
// 3.函数 【c】next之前-执行了.....
// 4.函数 【d】next之前-执行了.....
// 5.函数 【d】next之后-执行了.....
// 6.函数 【c】next之后-执行了.....
// 7.函数 【b】next之后-执行了.....
// 8.函数 【a】next之后-执行了.....
为了形象的理解,我们用以下伪代码表示
async function a (context,next) {
console.log('1.函数 【a】next之前-执行了.....')
async function b (context, next) {
console.log('2.函数 【b】next之前-执行了.....')
async function c (context, next) {
console.log('3.函数 【c】next之前-执行了.....')
async function d (context, next) {
console.log('4.函数 【d】next之前-执行了.....')
console.log('8.函数 【d】next之后-执行了.....')
}
console.log('7.函数 【c】next之后-执行了.....')
}
console.log('6.函数 【b】next之后-执行了.....')
}
console.log('5.函数 【a】next之后-执行了.....')
}
4.compose函数解析
// middleware 中间件函数数组, 数组中是一个个的中间件函数
function compose (middleware) {
return function (context, next) { // 调用compose函数会返回一个函数
let index = -1
return dispatch(0) // 启动middleware数组中的第一个函数的执行
function dispatch (i) {
index = i
let fn = middleware[i]
if (i === middleware.length) {
fn = next
}
if (!fn) return Promise.resolve()
// 这就是在执行 a,b,c ,d 函数, 在await next() 的时候回执行
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1) // 执行下一个中间件函数
}))
}
}
}
// composeMiddles1是一个函数, 这个函数包含 传入的所有中间件函数,且能依次执行这些中间件函数
var composeMiddles1 = compose([a,b,c,d])
composeMiddles() // 就是执行 dispatch(0)
dispatch(0) // 启动第一个函数的执行 也就是 a 函数
// a函数 中await next()
async function a (context,next) {
console.log('1.函数 【a】next之前-执行了.....')
await next() // 就是 function next () { return dispatch(i + 1) }
console.log('8.函数 【a】next之后-执行了.....')
}
await next()
// 就是执行的
dispatch(i + 1)
// dispatch(1) 也就是b函数
// 在b函数中有
await next()
// ......
// 这样就串起来依次执行了