Koa中间件现象
用 Koa
中间件(Middleware)
从现象出发,理解洋葱模型
按照 Koa
中间件规范,编写两个中间件
👇 创建 Koa
实例并挂载 logTime、logUrl
中间件
const Koa = require('koa')
const app = new Koa()
const logTime = require('./middleware/logTime')
const logUrl = require('./middleware/logUrl')
app.use(logTime())
app.use(logUrl())
// response
app.use(async ctx => {
ctx.body = 'Hello World'
})
app.listen(3000)
👇 调用这个中间件 会打印前/后
module.exports = function() {
return async function(ctx, next) {
console.log(
"next前,打印时间戳:",
new Date().getTime()
)
await next()
console.log(
"next后,打印时间戳:",
new Date().getTime()
)
}
}
👇 调用这个中间件 会打印上下文的url
module.exports = function() {
return async function(ctx, next) {
console.log("next前,打印url:", ctx.url)
await next()
console.log("next后,打印url:", ctx.url)
}
}
👆 以中间件代码上的next函数
为分界点:先 use
的中间件, next
前的逻辑先执行,但 next
后的逻辑反而后执行
🤔 这样相对于链式执行中间件有什么好处?
我们提出这样的需求 希望中间件修改后的状态能不关心挂载顺序,在任意中间件中读取到(不意味着中间件挂载顺序随意写,顺序决定中间件回调执行时机)
如:在第二个中间件 logUrl 里,对url加上了一个时间戳,然后在第一个中间件 logTime 里拿到这个时间戳url并打印
如果普通的链式执行所有中间件(一个回调函数),显然无法实现前面的中间件能使用之后的中间件所添加的东西 就需要在一个中间件里同时定义 1.顺序执行的回调函数 2.全部中间件执行后的回调函数 ,这样来共享一些单个作用域的变量
🤔 next函数是洋葱圈模型需要才用的吗?还是用于其他作用? 定义中间件是一个对象,对象内部提供pre函数和post函数
🤔 中间件pre的回调全部执行完,执行post回调的顺序为什么反过来
保留next分割中间件前后的功能,如中间1、2内部各自有next前/后,执行顺序为
- 中间件1的next前
- 中间件2的next前
- 中间件1的next后
- 中间件2的next后
手写Koa洋葱圈
创建中间件
const middleWare1 = (next) => {
console.log('first start')
next()
console.log('first end')
}
const middleWare2 = (next) => {
console.log('second start')
next()
console.log('second end')
}
const middleWare3 = () => {
console.log('third no next')
}
创建koa实例,提供use挂载中间件,run执行中间件
export createKoa() {
let middlewares = []
// 挂载中间件
const use = (middleWare) => {
middlewares.push(middleWare)
}
// 执行中间件
const run = () => { }
return {
use,
run
}
}
示例使用代码
import { createKoa } from 'mykoa'
const koa = createKoa()
koa.use(middleWare1)
koa.use(middleWare2)
koa.use(middleWare3)
koa.run()
编写执行中间件的run
👇 看起来直接遍历执行中间件数组就好
const next = () => { }
// 执行中间件
const run = () => {
middlewares.forEach( middleware => {
middleware(next) // 给中间件注入next
})
}
❌ 不能遍历执行,因为要由next函数来控制下一个执行
// 执行中间件
const run = () => {
let middleware1 = middlewares[0]
middleware1(function next(){
// next内部来执行下一个中间
let middleware2 = middlewares[1]
middleware2(function next(){
...
})
})
}
👆 是递归结构
// 执行中间件
const run = () => {
dispatch(0)
function dispatch (i) {
if (i === middlewares.length) return null // 跳出递归
const middleware = middlewares[i]
// 中间件注入next(每次都不一样)
const next = () => {
dispatch(i + 1) // 递归执行自身
}
middleware(next)
}
}
👆 可以发现我们只处理 next
下一个中间件(即函数执行到 next
前的逻辑内容),并没有在处理全部中间件之后,执行 next
后的步骤
其实在一个中间件执行 next
之后,执行顺序被传递到下一个中间 当这个 next
中断 也就开始执行最后一个中间件 next
下的代码块了 以此回到前一个中间件 next
下的代码块
对象结构中间件,双循环
🤔 那不使用 next函数风格方式
的对象结构的中间件遍历执行回调是否可行
👇 对象结构的中间件
const middleWare1 = {
pre: () => console.log('first start'),
post: () => console.log('first end')
}
const middleWare2 = {
pre: () => console.log('second start'),
post: () => console.log('second end')
}
const middleWare3 = () => console.log('third no next')
👇 执行中间件
const run = (middlewares) => {
middlewares.forEach(middleware => {
if(typeof middleware === 'function'){
middleware()
return
}
middleware.pre()
});
// 反转中间件数组遍历
middlewares.reverse().forEach(middleware => {
if(typeof middleware === 'function') return
middleware.post()
});
}
👆 循环2次和递归的时间复杂度应该是相同的,实现效果上也相同 如果需要中间件之间共享状态,也可以依赖被挂载的实例(Koa实例)变量实现
似乎递归形式的写法仅仅是更优雅,而不是这种使用场景的唯一解
MidWay
MidWay - A Node.js Serverless Framework for front-end/full-stack developers.
umi-request
umi-request - A request tool based on fetch
TODO: 源码笔记