模拟写一个 Koa
先看看 koa 咋用的?
const Koa2 = require('koa2');
const app = new Koa2();
app.use(async (ctx, next) => {
console.log('第一层洋葱 - 开始')
await next();
console.log('第一层洋葱 - 结束')
});
app.use(async (ctx, next) => {
console.log('第二层洋葱 - 开始')
const start = Date.now();
await next();
console.log('第二层洋葱 - 结束')
});
app.use(async ctx => {
console.log('第三层洋葱 - 开始')
ctx.body = 'Hello World';
console.log('第三层洋葱 - 结束')
});
app.listen(8000);
输出(经典的洋葱圈)
第一层洋葱 - 开始
第二层洋葱 - 开始
第三层洋葱 - 开始
第三层洋葱 - 结束
第二层洋葱 - 结束
第一层洋葱 - 结束
代码实现一个洋葱圈:
const http = require('http')
function compose(middlewareList) {
return function (ctx) {
function dispatch(i) {
const fn = middlewareList[i]
try {
return Promise.resolve(
fn(ctx, dispatch.bind(null, i + 1))
)
} catch (err) {
return Promise.reject(err)
}
}
return dispatch(0)
}
}
class LikeKoa2 {
constructor() {
this.middlewareList = []
}
use(fn) {
this.middlewareList.push(fn)
return this
}
createContext(req, res) {
const ctx = {
req,
res
}
ctx.query = req.query
return ctx
}
callback() {
const fn = compose(this.middlewareList)
return (req, res) => {
const ctx = this.createContext(req, res)
return fn(ctx)
}
}
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
}
module.exports = LikeKoa2
几个主要函数说明:
- use 把所有的中间件放入了一个数组中
- createContext 整合了 Node 中 http 的 req 和 res 参数到 ctx
- callback 函数最终返回的是提供给原生 http 模块的函数
- compose 中实现了具体洋葱圈的执行
我们来看一下 compose ,middlewareList 就是保存 use 函数要实现的全部中间件数组。从数组的第一个中间件一次执行, next 函数dispatch.bind(null, i + 1)
执行的源头就在这句代码。
function compose(middlewareList) {
return function (ctx) {
function dispatch(i) {
const fn = middlewareList[i]
try {
return Promise.resolve(
fn(ctx, dispatch.bind(null, i + 1))
)
} catch (err) {
return Promise.reject(err)
}
}
return dispatch(0)
}
}
洋葱圈中 compose 的伪代码执行顺序:
async (ctx, next) => {
console.log('第一层洋葱 - 开始')
await next();
async (ctx, next) => {
console.log('第二层洋葱 - 开始')
const start = Date.now();
await next();
async ctx => {
console.log('第三层洋葱 - 开始')
ctx.body = 'Hello World';
console.log('第三层洋葱 - 结束')
}
console.log('第二层洋葱 - 结束')
}
console.log('第一层洋葱 - 结束')
}
reduce 方法实现洋葱圈
function f1 (next) {
return function (action) {
console.log('第一次进入')
action = action * 2
next(action);
console.log('第一次退出')
}
}
function f2 (next) {
return function (action) {
console.log('第二次进入')
action = action + 20
next(action);
console.log('第二次退出')
}
}
function f3 (next) {
return function (action) {
console.log('第三次进入')
action = action / 4
next(action);
console.log('第三次退出')
}
}
let comp = [f1, f2, f3].reduce(
(a,b)=> {
return (...args) => {
return a(b(...args))
}
}
)((action)=>console.log(action))
comp(6) // 输出 8
我们看下执行过程:
第一次执行:
reduce 里面的函数
a = f1 , b = f2
(f1, f2) => {
return (...args) =>{
return f1(f2(...args))
}
}
返回的内容:
(...args) =>{
return f1(f2(...args))
}
第二次执行:
reduce 里面的函数
a = (...args) =>{
return f1(f2(...args))
}
b = f3
(a , f3 ) => {
return (...args) => {
return a(f3(...args))
}
}
把 a 换成第一次执行返回的函数, 第二次执行返回:
即:
(...args) =>{
return f1(f2(f3(...args)))
}
所以上述
[f1, f2, f3].reduce(
(a,b)=> {
return (...args) => {
return a(b(...args))
}
}
)
返回的是一个函数, 我们把它命名为 resultFunc:
const resultFunc = (...args) =>{
return f1(f2(f3(...args)))
}
最后这样执行 resultFunc( (action)=>console.log(action) )
其实执行的就是 f1(f2(f3((action)=>console.log(action)))), 这是一个可执行函数
之后,
let comp = f1(f2(f3((action)=>console.log(action))))
给 comp 传入一个参数 6 ,即 f1(f2(f3((action)=>console.log(action))))(6)
一个洋葱圈的流程就走完了。
redux
redux 中 compose 方法
export default function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
redux 中 applyMiddleware 的部分代码
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
/* 省略一部分代码 */
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
/* 省略一部分代码 */
}
}
我们发现,redux 也是基于 reduce 来对中间件进行执行的。redux 其他内部实现篇幅限制,本文不作详述。
参考:
- https://www.runoob.com/jsref/jsref-reduce.html
- https://blog.csdn.net/qq_41499782/article/details/116257693?utm_medium=distribute.pc_feed_404.none-task-blog-2defaultBlogCommendFromBaiduRate-3-116257693-blog-null.pc_404_mixedpudn&depth_1-utm_source=distribute.pc_feed_404.none-task-blog-2defaultBlogCommendFromBaiduRate-3-116257693-blog-null.pc_404_mixedpud
- http://www.javashuo.com/article/p-zdzgdukb-eo.html