函数组合
函数组合在Redux,Koa源码中都有使用,本文就来说一下什么是函数组合
引出函数组合
function add(x) {
return x + 1
}
function square(z) {
return z * z
}
上面定义了两个简单的函数,add函数和square函数,怎么通过上面两个函数实现加1后再平方呢?很简单,我们可以这样实现:
const fn = (x) => square(add(x))
上看是两个函数的组合很简单,我们怎么实现未知函数个数的组合,这在中间件中是很常见的,比如Koa的中间件你可以写任意个,通过怎么的方法能够实现未知个数的函数组合呢?
实现 compose 方法一:
const compose = (...[first, ...other]) => (...args) => {
let ret = first(...args)
other.forEach(fn => {
ret = fn(ret)
})
return ret
}
上面的实现就比较直观了,通过参数结构的方法,把传入的函数进行分组,first就是传入的第一个函数,other就是剩下的所有函数的集合,把上一个函数的执行结果作为下一个函数参数,一次执行多个函数。
实现 compose 方法二:
function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
方法二简洁了很多
此写法的函数执行顺序是从右向左,也就是 compose(square, add)(1), 先执行add(1), 执行结果作为square参数再执行square 如果想让执行顺序从左向右,只需要把compose方法里的reducer改为reduceRight即可
上面add,square函数都是同步的,那么异步的函数如何实现呢?
异步 compose 函数
function compose(middlewares) {
return function() {
return dispatch(0)
function dispatch(i) {
let fn = middlewares[i]
if (!fn) {
return Promise.resolve()
}
return Promise.resolve(
fn(function next() {
return dispatch(i + 1)
})
)
}
}
}
通过异步函数来验证一下上面函数的执行结果
async function fn1(next) {
console.log('fn1')
await next()
console.log('end fn1')
}
async function fn2(next) {
console.log('fn2')
await delay()
await next()
console.log('end fn2')
}
function fn3(next) {
console.log('fn3')
}
function delay() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 2000)
})
}
const middlewares = [fn1, fn2, fn3]
const finalFn = compose(middlewares)
finalFn()
执行结果如下:
fn1
fn2
fn3
end fn2
end fn1
总结:
通过 compose 函数可以实现多个中间件的一次执行,这就是Redux,Koa中间件的实现原理。