koa洋葱模型,koa-compose函数

3,251 阅读4分钟

本文介绍:

  1. koa如何处理请求的
  2. koa-compose函数

1.先看两幅图

这是大家常见的koa洋葱模型图

koa web服务器接受到一个api请求;需要经过一个个的处理函数(请求 -> |fn1| -> |fn2| -> |fn...| -> 响应)。如何把这一个个的处理函数串联起来依次执行。在koa中依靠的就是koa-compose这个函数。

如下图所示,一个请求进来需要经过权限校验、controller层、service层等等,经过各层函数(中间件的处理)之后才会将结果返回给接口调用方。

2.相关源码

先起一个koa服务,通过下面这个demo,配合源码看一下koa是如处理请求的。

const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
  console.log('---1--->')
  await next()
  console.log('===6===>')
})
app.use(async (ctx, next) => {
  console.log('---2--->')
  await next()
  console.log('===5===>')
})
app.use(async (ctx, next) => {
  console.log('---3--->')
  await next()
  console.log('===4===>')
})

app.listen(8899, () => {
  console.log('应用启动了')
})

// 执行结构结果就是 1  2  3  4  5 6

通过源码来分析一下,koa是如何处理请求的。一下源码在 koa/lib/application.js目录下

1. app.listen()

  listen(...args) {
    const server = http.createServer(this.callback());
    // 调用Node原生的http.createServer([requestListener]) 
    // 参数requestListener是请求处理函数,用来响应request事件;
	//此函数有两个参数req,res。当有请求进入的时候就会执行this.callback函数
    return server.listen(...args); // port host等信息
  }

2. 请求处理函数 this.callback()

  callback() {
  // 将中间件函数转化为compose函数
    const fn = compose(this.middleware); 
    if (!this.listenerCount('error')) this.on('error', this.onerror);
	// request事件的响应函数,就是callback函数
    const handleRequest = (req, res) => { 
    	// 根据req, res创建 上下文
      const ctx = this.createContext(req, res); 
      // 最终会调用handleRequest方法,将上下文以及compose函数传入
      return this.handleRequest(ctx, fn); 
    };
    return handleRequest;
  }

3. 中间件函数数组 this.middleware

 use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    // 在调用use的时候,把函数加到middleware数组中
    this.middleware.push(fn); 
    return this;
  }

4. callback函数实体 this.handleRequest

 handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err); // 对错误进行处理
    const handleResponse = () => respond(ctx); // 对返回的结果进行处理
    onFinished(res, onerror); 
    // 去依次执行compose函数,如何依次执行的?
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

整个过程的核心就是调用compose函数,将中间件函数数组转化为compose函数 const fn = compose(this.middleware);然后调用fn,就会依次执行所有的中间件函数;且上下文ctx会贯穿所有的中间件函数。

3.compose函数

首先看一下 koa-compose函数源码

function compose (middleware) {
  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!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    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 {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

将一些注释容错判断等代码删除掉,可以发现koa-compose函数就十多行代码

function compose (middleware) {
  return function (context, next) {
    let index = -1
    return dispatch(0) 
    function dispatch (i) {
      index = i
      let fn = middleware[i] 
      if (i === middleware.length) {
        fn = next
      }
      if (!fn) return Promise.resolve()
      return Promise.resolve(fn(context, function next () { 
        return dispatch(i + 1)
      }))
    }
  }
}

通过以下4个简单的函数,看一下koa-compose函数使用效果

async function a (context,next) {
  console.log('1.函数 【a】next之前-执行了.....')
  await next()
  console.log('8.函数 【a】next之后-执行了.....')
}
async function b (context, next) {
  console.log('2.函数 【b】next之前-执行了.....')
  await next()
  console.log('7.函数 【b】next之后-执行了.....')
}
async function c (context, next) {
  console.log('3.函数 【c】next之前-执行了.....')
  await next()
  console.log('6.函数 【c】next之后-执行了.....')
}

async function d (context, next) {
  console.log('4.函数 【d】next之前-执行了.....')
  console.log('5.函数 【d】next之后-执行了.....')
}

执行查看结果

var composeMiddles1 = compose([a,b,c,d])
composeMiddles()
// 执行结果
// 1.函数 【a】next之前-执行了.....
// 2.函数 【b】next之前-执行了.....
// 3.函数 【c】next之前-执行了.....
// 4.函数 【d】next之前-执行了.....
// 5.函数 【d】next之后-执行了.....
// 6.函数 【c】next之后-执行了.....
// 7.函数 【b】next之后-执行了.....
// 8.函数 【a】next之后-执行了.....

为了形象的理解,我们用以下伪代码表示

async function a (context,next) {
  console.log('1.函数 【a】next之前-执行了.....')

  async function b (context, next) {
    console.log('2.函数 【b】next之前-执行了.....')

    async function c (context, next) {
      console.log('3.函数 【c】next之前-执行了.....')

      async function d (context, next) {
        console.log('4.函数 【d】next之前-执行了.....')
        console.log('8.函数 【d】next之后-执行了.....')
      }

      console.log('7.函数 【c】next之后-执行了.....')
    }

    console.log('6.函数 【b】next之后-执行了.....')
  }

  console.log('5.函数 【a】next之后-执行了.....')
}

4.compose函数解析


// middleware 中间件函数数组, 数组中是一个个的中间件函数
function compose (middleware) { 
  return function (context, next) { // 调用compose函数会返回一个函数
    let index = -1
    return dispatch(0) // 启动middleware数组中的第一个函数的执行
    function dispatch (i) {
      index = i
      let fn = middleware[i]
      if (i === middleware.length) {
        fn = next
      }
      if (!fn) return Promise.resolve()
      // 这就是在执行 a,b,c ,d 函数, 在await next() 的时候回执行
      return Promise.resolve(fn(context, function next () { 
        return dispatch(i + 1)  // 执行下一个中间件函数
      }))
    }
  }
}

// composeMiddles1是一个函数, 这个函数包含 传入的所有中间件函数,且能依次执行这些中间件函数
var composeMiddles1 = compose([a,b,c,d]) 
composeMiddles() // 就是执行 dispatch(0)

dispatch(0) // 启动第一个函数的执行 也就是 a 函数

// a函数 中await next() 
async function a (context,next) {
  console.log('1.函数 【a】next之前-执行了.....')
  await next() // 就是 function next () {  return dispatch(i + 1) }
  console.log('8.函数 【a】next之后-执行了.....')
}

await next()  
// 就是执行的
dispatch(i + 1) 

// dispatch(1) 也就是b函数

// 在b函数中有
await next()

// ......
// 这样就串起来依次执行了