手搓一个简单的koa

71 阅读1分钟

1. 最近学习后端,发现koa里边有个use函数挺有意思

const MiniKoa =require("./koa")
const app =new MiniKoa();
app.use((ctx,next)=>{
    console.log(1);
    ctx.body="123";
     next();
    console.log(2);

})
app.use((ctx,next)=>{
    console.log(3);
    ctx.body+="123"
     next();
    console.log(4);

})
app.listen(7999)

当请请求到达node服务器时 依次打印 1 3 4 2 ,神奇就神奇在这个洋葱模型,由外而内再由内而外

Snipaste_2022-08-16_18-17-45.ico

隐隐约约感觉这个use是把函数推进一个队列,然后next指向下一个中间件

2.根据外部调用实现里边的封装,一下是两个关键点

2.1 context 上下文的封装 (上下文应该是每次请求都不一样,所以一定是用new的方式)

class Ctx {
    constructor(req, res) {
        this.req = req;
        this.res = res;
        this.body = null;
    }
}

2.2 next函数的实现用的是compose封装

function compose(middlewares) {
    return function (ctx) {
        const dispatch = (i) => {
            if (i == middlewares.length) {
                return ()=>{}
            }
            else {
                //参照中间件写法 app.use( (ctx,next)=>{***});
                return middlewares[i](ctx, ()=>{dispatch(i + 1)})
            }
        }
        //返回第一个中间件
        return dispatch(0)
    }
}

2.3 koa的封装

class MiniKoa {
    constructor() {
        this.middlewares = []
    }

    listen(...arg) {
        var server = http.createServer(async (req, res) => {
           
            //console.log("请求来了",req.url)
            
            const ctx = new Ctx(req, res)
            const fn = compose(this.middlewares)
            
            try {
                await fn(ctx)
                ctx.res.end(ctx.body)
            } catch (err) {
                this.onerror(ctx, err)
            }
            
        })

        server.listen(...arg)
    }
    onerror(ctx, err) {
        console.log(err)
    }
    
    //没错 use就是把所有的中间件推进队列
    use(fn) {
        if (Object.prototype.toString.call(fn) === '[object Function]') {
            this.middlewares.push(fn)
        } else {
            throw new Error('use arguments must be a function')
        }
    }

}

3.完整代码

const http = require("http")


class Ctx {
    constructor(req, res) {
        this.req = req;
        this.res = res;
        this.body = null;
    }
}

//多函数连接式
function compose(middlewares) {
    return function (ctx) {
        const dispatch = (i) => {
            if (i == middlewares.length) {
                return ()=>{}
            }
            else {
                return middlewares[i](ctx, ()=>{dispatch(i + 1)})
            }
        }
        return dispatch(0)
    }
}

class MiniKoa {
    constructor() {
        this.middlewares = []
    }

    listen(...arg) {
        var server = http.createServer(async (req, res) => {
            // res.end(JSON.stringify({ code: 'ok', data: { user: "张三" } }))
            console.log("请求来了",req.url)
            const ctx = new Ctx(req, res)
            const fn = compose(this.middlewares)
            try {
                await fn(ctx)
                ctx.res.end(ctx.body)
            } catch (err) {
                this.onerror(ctx, err)
            }
        })

        server.listen(...arg)
    }
    onerror(ctx, err) {
        console.log(err)
    }
    use(fn) {
        if (Object.prototype.toString.call(fn) === '[object Function]') {
            this.middlewares.push(fn)
        } else {
            throw new Error('use arguments must be a function')
        }
    }

}
module.exports= MiniKoa