JS中的洋葱模型

4,338 阅读2分钟

洋葱模型是一种很经典的程序设计思路:

function compose (middleware) {
  // some code
}

它能接受一个函数数组,然后返回一个新的函数,达到这样的效果:

let middleware = []
middleware.push((next) => {
	console.log(0)
	next()
	console.log(3)
})
middleware.push((next) => {
	console.log(1)
	next()
	console.log(1.1)
})
middleware.push(() => {
    console.log(2)
})

let fn = compose(middleware)
fn()

/*
0
1
2
1.1
3
*/

当我们尝试执行fn的时候,它会按照顺序调用之前函数数组中的函数,并且给每一个小函数传递一个参数: next函数。

如果在小函数中执行next,就会调用这个函数的下一个函数,如果没有执行next,程序就不会往下走。所以你可以看到上面的打印顺序。

但是问题来了,我们怎么写这个compose函数?

首先, 这个compose函数肯定会返回一个大的函数嘛,所以:

function compose (middleware) {
  return function () {
  }
}

然后返回的函数是fn,如果我们尝试执行fn, fn将会尝试执行middleware[0],同时就想我们之前说的,它会给这个小函数传入一个next函数

function compose (middleware) {
  return function () {
    let f1 = middleware[0]
    f1(function next(){
    })
  }
}

然后这个next函数有开关的功能,如果我们执行next函数,next函数就会调用数组中下一个函数,所以

function compose (middleware) {
  return function () {
    let f1 = middleware[0]
    f1(function next(){
      let f2 = middleware[1]
      f2(function next(){
        ...
      })
    })
  }
}

然后。。。这是递归!为了让上面的更优雅,我们可以这样写

function compose (middleware) {
   return function () {
      dispatch(0)
      function dispatch (i) {
         const fn = middleware[i]
         if (!fn) return null
         fn(function next () {
            dispatch(i + 1)
         })
      }
   }
}

通过一个dispatch来优雅的实现递增。

然后我们还希望这个函数支持异步函数:

function compose (middleware) {
   return async function () {
      await dispatch(0)
      function async dispatch (i) {
         const fn = middleware[i]
         if (!fn) return null
         await fn(function next () {
            dispatch(i + 1)
         })
      }
   }
}

最后一步,允许用户传递参数

function compose (middleware) {
   return async function () {
      let args = arguments
      await dispatch(0)
      function async dispatch (i) {
         const fn = middleware[i]
         if (!fn) return null
         await fn(function next () {
            dispatch(i + 1)
         }, ...args)
      }
   }
}

参考: koa-compose

源代码: github