常见的函数组合
函数组合在我们的实际开发中经常会碰到。我们来看看他们的实现方式。
普普通通的 compose
普普通通的函数组合长这样。返回了一个方法,方法里 a 接收了 b 的执行结果,然后返回 a 的执行结果。
function compose (a, b) {
return function (...args) {
return a(b(...args))
}
}
看个简单的用法
const addition = num => num + 1
const multiply = num => num * num
使用 compose ,就看的更加简洁了些。
const composeFunc = compose(multiply, addition)
composeFunc(2)
但是目前的 compose 方法并不能满足两个方法以上的组合,我们看看常用redux 和 koa 的实现。
redux compose
这是 redux 的实现写法
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)))
}
第一眼看,可能有些难以理解。
const composeFunc = compose(a, b, c, d)
我们换一个手动的写法就相当于
const composeFunc = (...args) => a(b(c(d(...args))))
他的入参从右往左执行,最右边的 d 方法接受 args 参数,之后的每一个接受前一个返回结果作为参数。直到 a 执行返回结果。
在 redux 中,主要用于组合多个 store 增强器,依次执行。
所有 compose 工作就是让你编写深度嵌套的函数时,避免了向右偏移(使用深度右括号)。
我们思考一个问题,如何实现一个更符合直觉的从左往右执行的组合函数呢?
koa-compose
这是 koa 的实现写法。也是洋葱模型的核心。
function compose (middleware) {
// 规定 middleware 必须是数组,且都是 function
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!')
}
return function (context, next) {
let index = -1
// dispatch 从 middleware 的第一个方法开始执行
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
}
// 边界处理
if (!fn) {
return Promise.resolve()
}
try {
// 将 context 和 下一个中间件方法(next)传入到当前的中间件方法。
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
这里插一句,我们在写 koa 时,会调用 app.use(...)
实际上就是往 middleware
里 push
方法。
我们看一看具体用法。
async function foo (context, next) {
context.number += 1
console.log(`step 1 : ${context.number}`)
await next()
context.number += 1
console.log(`step 4 : ${context.number}`)
}
async function bar (context, next) {
context.number += 1
console.log(`step 2 : ${context.number}`)
await next()
context.number += 1
console.log(`step 3 : ${context.number}`)
}
使用 compose 组合上面的方法。
const composeFun = compose([foo, bar])
composeFun(
{ number: 0 },
(context) => {
console.log(`center : ${context.number}`)
}
)
输出:
step 1 : 1
step 2 : 2
center : 2
step 3 : 3
step 4 : 4
在日常开发中,这种方式可以用在多个接口互相依赖等场景,用这样的方式去写,可以解耦代码逻辑,更加清晰。