洋葱模型

koa2最大的特点是独特的中间件流程控制。在其源码中使用koa-compose:

const compose = require('koa-compose')

正是基于这个中间件,才能保证每个async/await返回的promise的执行顺序得到保证。

其核心思想是:当遇到await next()时,中断当前中间件的转而执行下一个中间件,当执 行到最后一个中间件时,再倒序执行next()之后的代码。

下图很形象的展示了此过程,即洋葱模型。

洋葱模型

首先koaapp.listen使用this.callback()来包裹 node 的httpServer的回调函数 :

listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
}

this.callback()里,用comopse处理middleware这个中间件的集合:

  callback() {
    const fn = compose(this.middleware);
	// this.middleware是一个数组,所有的中间件都被push进去,在use函数会做这一步。
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };
    return handleRequest;
  }

comopse这个方法在koa-compose源码:

function compose(middleware) {
    /**
     * @param {Object} context
     * @return {Promise}
     */
    return function (context, next) {
        // last called middleware #
        let index = -1
        return dispatch(0)
        // dispatch函数遍历整个middleware数组
        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 // 执行到最后一个中间件,则执行next(),逆序。
            if (!fn) return Promise.resolve()
            try {
                // 把dispatch(i+1)传给`middleware`中的方法
                // 将context一路传给中间件
                return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
            } catch (err) {
                return Promise.reject(err)
            }
        }
    }
}

compose函数接受中间件的集合(Array),返回一个promise,这个返回的promise带 着context对象一直在中间件传递。

关于compose函数的分析,假设有三个m1 m2 m3三个函数:

async function m1(next) {
    console.log('m1')
    await next()
}
async function m2(next) {
    console.log('m2')
    await next()
}
async function m3(next) {
    console.log('m3')
}

我们希望构造一个函数来确保m1 m2 m3函数顺序执行。即执行m1完毕后 ,await next()调用m2,即我们需要将m2作为参数传给m1,即将middleware中的 下一个中间件fn作为未来next的返回值:

let next1 = async function () {
    await m3()
}
// 进一步 m2 执行完调用m3
let next2 = async function () {
    await m2(next1)
}
m1(next2)

对于 n 个async来说,可以将产生nextn函数抽象为一个函数:

const createNext = (middleware, oldNext) => {
    return async (_) => {
        await middleware(oldNext)
    }
}

let next1 = createNext(m3, null)
let next2 = createNext(m2, next1)
let next3 = createNext(m1, next2)
next3()

再次将middleware作为数组来精简:

let middlewares = [m1, m2, m3]
let len = middlewares.lenght
// 最后一个中间件的next
let next = async function () {
    return Promise.resolve()
}
for (let i = len - 1; i > 0; i--) {
    next = createNext(middlewares[i], next)
}
next()