笔者最近读了一些express 和koa 的源码,发现:
- 其实并非只有koa 实现了洋葱模型
- 实际上express 也有洋葱模型的概念,但在执行异步函数next 的时候,express很难在前面触发的中间件中拿到结果
- 但他俩有什么不同呢?这就是我接下来分析出来的结果
- 话不多说,上才艺:
- 模仿express next 执行流程,粗略写了一下express中 next 函数的执行过程
// fn1、fn2、fn3 皆为中间件
async function fn1(ctx, next) {
console.log('fn1 start')
ctx.message += 'aaa'
await next()
console.log('fn1 end', ctx.message)
}
async function fn2(ctx, next) {
console.log('fn2 start')
ctx.message += 'bbb'
await next()
console.log('fn2 end')
}
async function fn3(ctx, next) {
console.log('fn3 start')
const res = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ccc')
}, 2000)
})
ctx.message += res
console.log('fn3 end')
}
const middlewares = [fn1, fn2, fn3]
const context = {
message: ''
}
// express 会将每个中间件都封装成一个layer 函数,这里只是模拟执行过程,所以简化版代码如下
let index = 0
next()
function next() {
if (index > middlewares.length) return
const fn = middlewares[index]
index++
fn(context, next)
}
- 模仿koa 的next 函数执行过程
async function fn1(ctx, next) {
console.log('fn1 start')
ctx.message += 'aaa'
await next()
console.log('fn1 end', ctx.message)
}
async function fn2(ctx, next) {
console.log('fn2 start')
ctx.message += 'bbb'
await next()
console.log('fn2 end')
}
async function fn3(ctx, next) {
console.log('fn3 start')
const res = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ccc')
}, 2000)
})
ctx.message += res
console.log('fn3 end')
}
// 模拟context数据,koa 会内部封装这个对象,此处不需要考虑那么复杂
const context = {
message: ''
}
// koa 处理中间件的时候很简单,只是将其扔到内部的 middlewares 数组中
const middlewares = [fn1, fn2, fn3]
let index = 0
// 框架会帮助调用第一个中间件
dispatch(0)
function dispatch(index) {
if (index > middlewares.length) return
const fn = middlewares[index]
// 这俩是执行结果处理函数,可以忽略
const onSuccess = res => console.log('success')
const onError = err => console.log('error')
// 这里是重点!!!,返回的是promise
return Promise.resolve(
fn(context, dispatch.bind(null, ++index))
).then(onSuccess).catch(onError)
}
- 对比之下,不难发现,express中的next函数并没有返回任何东西(当然,undefined不用说都懂)
- 而koa 中的next 函数本质就是代码中的dispatch函数,每次触发调用next都会去调用diapatch去处理,最终的结果会被包裹一层promise return出去
- Promise 中的类方法实现过程,请参考,这边不多说
- 总之,我们调用next执行下一个中间件的时候可以使用 async 和 await来处理异步代码,这样就可以从靠前执行的中间件中拿到异步数据进行下一步处理
最后,欢迎留言、讨论,笔者会持续关注viewers的动态信息,以便及时更新文档