前言
- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 这是源码共读的第17期,链接:第5期 | koa-compose | 相对较难,觉得难可跳过
koa-compose
源码地址: koa-compose
代码地址: koa-compose/index.js
可以使用项目里的 test/test.js测试
源码解析
'use strict'
/**
* Expose compositor.
*/
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
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
// 如果迭代完了所有的中间件则返回一个成功的Promise
if (!fn) return Promise.resolve()
try {
// 执行fn(即当前中间件)并且设置下标指向下一个中间件
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
代码不多,核心是
- 接收一个中间件数组
- 返回一个迭代器,该迭代器负责迭代中间件数组
- 根据下标来执行异步中间件,每执行一个中间件,就把数组下标后移,并从数组中取出下一个作为下一次迭代的项,然后等待当前项异步执行
洋葱模型
const Koa = require('koa')
const app = new Koa()
// Response 时间
app.use(async (ctx, next) => {
const start = Date.now()
console.log(1)
await next()
const ms = Date.now() - start
ctx.set('X-Response-Time', ms)
console.log(5)
})
// logger
app.use(async (ctx, next) => {
const start = Date.now()
console.log(2)
await next()
const ms = Date.now() - start
// 写入logger
// log(`${ctx.method} ${ctx.url} - ${ms}`);
console.log(4)
})
// 设置body
app.use(async (ctx) => {
ctx.body = 'Koa 洋葱模型'
console.log(3)
})
这代码会依次打印: 1 2 3 4 5
可以看到洋葱模型是结合了 async await 实现的
总结
这个中间件迭代器代码不多,核心原理归结如下
- 实现一个函数
compose,compose接收一个中间件数组middleware,返回一个中间件迭代器 - 迭代器接收两个参数:上下文
context, 下一个中间件next - 迭代器内部有个
dispatch函数,该函数接受middleware下标,负责执行下标指定的中间件,并且将下标指向下一个中间件(next的由来),如果没有下一个了则返回Promise.resolve() next的调用时机是在上一个中间件里处理完它的事务后负责调用的,由此实现了整个异步迭代
关于洋葱模型,这里是 async await带来的特性,因为每个中间件 await next() 前的代码直接执行,后面的代码都需要等待下一个中间件next所有代码执行完才执行,这才形成了完整的洋葱调用模型