JS 中的函数组合

373 阅读2分钟

函数组合

函数组合在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中间件的实现原理。