Skip to content

koa-compose 源码解析

前言

接触过 nodejs 就很容易会了解到 koa,然后在 koa 中的中间件机制洋葱模型,那绝对是优美的实现。因为洋葱模型,让中间件可以在请求前后做更多的事

中间件是什么

简单点理解的话,其实就是一个函数,可以简单看下 koa 的使用例子

javascript
const koa = require('koa')
const app = new koa()

app.use(async (ctx, next) => {
  console.log('第一个中间件开始')
  next()
  console.log('第一个中间件结束')
})
app.use(async (ctx, next) => {
  console.log('第二个中间件开始')
  next()
  console.log('第二个中间件开始')
})

app.use((ctx, next) => {
  console.log('第三个中间件开始')
  next()
  console.log('第三个中间件结束')
})

app.use((ctx) => {
  console.log('响应')
  ctx.body = '200'
})

app.listen(7001)

第一个中间件开始
第二个中间件开始
第三个中间件开始
响应
第三个中间件结束
第二个中间件结束
第一个中间件结束

看顺序基本知道 next 函数前面的依次执行完,然后在返回去依次执行 next 下面的部分

响应

这里疑惑了一下,由于惯性思维,没有去了解太多,以为 ctx.body 在这里就返回给客户端了。然而并不是,因为如果现在返回了,那中间件 next 下方的代码就没有什么意义了,因为请求已经结束,所以去看了 koa 的源码,响应是在中间件全部处理结束之后才返回

把中间件处理的结果交给返回体函数

这里和 express 的中间件就有区别了,express 的中间件是在最内部的函数返回的,然后请求就直接结束了

中间件源码

javascript
'use strict'

/**
 * Expose compositor.
 */

module.exports = compose

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

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) {
      //判断函数下标,不让next多次调用
      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 {
        执行当前函数并传入下一个函数并返回promise
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

如有转载或 CV 的请标注本站原文地址