Koa 的洋葱模型

145 阅读2分钟

一、洋葱模型理解

Koa的洋葱模型是以next()函数为分割点,先由外到内执行Request的逻辑,然后再由内到外执行Response的逻辑。使用compose()实现一个dispatch递归,将后一个中间件函数当作参数传入前一个中间件函数,从而实现洋葱模型。

graph TB
a("use()收集中间件")
b("listen()创建http服务")
b1("chllback()创建了ctx并调用compose生成中间件函数")
b2("handleRequest()运行中间件函数并处理成功失败回调")
c("递归中间件函数,执行前一个中间件并向其传入后一个中间件函数")
subgraph compose
    c
end
subgraph listen
    b --> b1 --> b2
end
subgraph use
    a
end

二、什么是洋葱模型

const Koa = require('koa');
const app = new Koa();

// 中间件1
app.use((ctx, next) => {
    console.log(1);
    next();
    console.log(2);
});

// 中间件 2 
app.use((ctx, next) => {
    console.log(3);
    next();
    console.log(4);
});

app.listen(8000, '0.0.0.0', () => {
    console.log(`Server is starting`);
});

输出的结果是:

1
3
4
2

next() 方法上面部分会先执行,下面部门会在后续中间件执行全部结束之后再执行。 也就是说每一个中间件都有两次处理时机

三、深入理解洋葱模型

1、use()

use 方法就是得到 middleware 中间件数组

use(fn) {
    this.middleware.push(fn);
    return this;
}

2、listen()callback()创建http服务

listen()就是使用http创建一个服务,其中chllback()就是http服务的请求处理函数。 callback()是创建了ctx上下文环境,以及调用compose()函数生成一个中间件函数,然后调用handleRequest()

handleRequest()函数主要是执行中间件函数并处理结果

listen(...args) {
    // node http 创建一个服务
    const server = http.createServer(this.callback());
    return server.listen(...args);
}
callback() {
    // 返回值是一个函数
    const fn = compose(this.middleware);
    const handleRequest = (req, res) => {
        // 创建 ctx 上下文环境
        // const ctx = this.createContext(req, res);
        const ctx = {
            req,
            res
        };
        return this.handleRequest(ctx, fn);
    };
    return 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((res) => {
            console.log("res:", res);
        })
        .catch(onerror);
}

3、compose()生成中间件函数

compose()中有一个dispatch()函数进行递归,目的是执行前一个中间件并向其传入后一个中间件函数,所以在use()收集的中间件函数调用next()方法会执行下一个中间件。

compose()是使用了koa-compose库,基本逻辑如下。

function compose(middleware) {
    return function (context, next) {
        // last called middleware #
        let index = -1;
        // 一开始的时候传入为 0,后续会递增
        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;
            // 当 fn 为空的时候,就会开始执行 next() 后面部分的代码
            if (!fn) return Promise.resolve();
            try {
                // 执行中间件,留意这两个参数,都是中间件的传参,第一个是上下文,第二个是 next 函数
                // 也就是说执行 next 的时候也就是调用 dispatch 函数的时候
                return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
            } catch (err) {
                return Promise.reject(err);
            }
        }
    };
}

参考

【Node】深入浅出 Koa 的洋葱模型