使用JavaScript实现简单洋葱模型的记录
什么是洋葱模型
洋葱模型是一种中间件的设计模式,它的核心思想是将各种操作的处理分为前置处理、后置处理和中间处理三个阶段 ,操作在这三个阶段之间依次传递。
话不多说直接上测试
it('use', () => {
const onion = new Onion()
const fn = async (ctx: any, next: () => Promise<void>) => {
await next()
ctx.count++
}
onion.use(fn)
expect(onion.middlewares.length).toBe(1)
})
实现以及一些初始化
type OnionMiddlewareFn = (ctx: any, next: () => Promise<void>) => Promise<void>
class Onion<T extends any> {
// 1. 首先定义一个中间件数组
middlewares: OnionMiddlewareFn[]
auto: boolean
constructor(auto: boolean = true) {
this.auto = auto
this.middlewares = []
}
// 2. 定义一个use方法,用来注册中间件
use(fn: OnionMiddlewareFn) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
this.middlewares.push(fn)
}
}
这些都是一些基本操作,提供一个 use 方法用来注册中间件 ,一个 middlewares 数组用来存储中间件。 在简单做一下预防,防止用户传入的不是函数。
上基础运行的测试
it('run', async () => {
const onion = new Onion()
const ctx = { count: 0 }
onion.use(async (ctx: any, next: () => Promise<void>) => {
ctx.count++
await next()
ctx.count++
})
onion.use(async (ctx: any, next: () => Promise<void>) => {
ctx.count++
await next()
ctx.count++
})
await onion.run(ctx)
expect(ctx.count).toBe(4)
})
这个测试需要我们实现一个 run 方法并且运行中间件的逻辑,这个逻辑是一个递归调用的过程,每次调用都会执行一个中间件,直到中间件数组为空。
懒得讲了,反正没人看 谁看到了,谁慢慢看,反正不难
compose(middlewares: OnionMiddlewareFn[]) {
let index = -1
let auto = this.auto
return (context: T | undefined) => {
return async function dispatch(i: number): Promise<void> {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middlewares[i]
if (!fn) return Promise.resolve()
try {
let current = i
await Promise.resolve(fn(context, dispatch.bind(null, index + 1)))
if (auto && i === current) {
dispatch(index + 1)
}
} catch (err) {
return Promise.reject(err)
}
}
}
}
// 4. 定义一个run方法,用来执行中间件
async run(ctx?: T) {
return this.compose(this.middlewares)(ctx)(0)
}
具体实现的仓库地址:onion