【JS】洋葱圈:Koa 到 reduce 再到 redux 的实现

本文深入解析洋葱圈模型在Koa和Redux中的应用。通过示例代码展示了如何在Koa中构建洋葱圈中间件执行流程,并详细解释了Redux中compose函数的工作原理,揭示了其如何利用reduce方法实现中间件的串联执行。同时,文章还提及了Redux applyMiddleware在中间件管理中的作用。
摘要由CSDN通过智能技术生成

模拟写一个 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

image.png

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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值