Node 浅析koa2中间件

koa2采用asyncawait来处理异步,koa2实例的use函数的参数都是中间件。

先来看一个koa2的核心小demo

// 中间件的仓库
const arr = [
    async (next) => {
        console.log(1)
        await next()
        console.log(2)
    },
    async (next) => {
        console.log(3)
        await new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(
                    console.log(4)
                )
            }, 1000)
        }) // 异步操作 await 会等待后面的promise resolve 后再向下执行
        await next()
        console.log(5)
    },
    async (next) => {
        console.log(6)
    },
    async (next) => {
        // 不会执行 因为上一个函数中没有执行next
        console.log(7)
        await next()
        console.log(8)
    },
    async (next) => {
        // 不会执行 因为前面的函数中没有执行next
        console.log(9)
    }
]

function fun(arr) {
    function dispose(index) {
        const currentFun = arr[index]
        const next = dispose.bind(null, index + 1)
        return currentFun(next) // 尾递归
    }

    dispose(0)
}

fun(arr) // 先打印 1 3 一秒后打印4 6 5 2

code开始 (新建一个ware.js文件)

const http = require('http')
const urlParser = require("url") // 解析url字符串和url对象

class Middleware {
    constructor() {
        this.wares = []  // 存储中间件
    }

    use(fun) {
        this.wares.push(fun) // 收集中间件
        return this
    }

    /* 中间件处理的核心 */
    handleMiddleware(wareList) {
        return ctx => {
            // 中间件调用
            const dispose = index => {
                const currentFun = wareList[index]
                return new Promise((resolve, reject) => {
                    try {
                        // 使用Promise.resolve 包装 currentFun 防止外部传入的currentFun为一个普通函数
                        /* dispose.bind(null, index + 1)就是next 让dispose继续执行下一个中间件
                            如果没有在中间件中调用dispose.bind(null, index + 1) 则不会再去获取下一个中间件
                        */
                        return resolve(currentFun(ctx, dispose.bind(null, index + 1)))
                    } catch (e) {
                        return Promise.reject(e)
                    }
                })
            }

            // 立即执行一下仓库的第一个中间件
            dispose(0)
        }
    }

    createContext(req, res) {
        const {method, url} = req
        const {query} = urlParser.parse(url, true)

        // ... 这里远比这个复杂, 我们只做一个简单的包装
        return {
            method, url, query,
            res
        }
    }

    serverHandle() {
        return (req, res) => {
            // 当请求来的时候我们去触发中间件
            const fn = this.handleMiddleware(this.wares)
            // 得到当前请求的上下文对象
            const ctx = this.createContext(req, res)
            fn(ctx)
        }
    }

    listen(...args) {
        const app = http.createServer(this.serverHandle()) // 这里只是为了模拟得到一个http服务
        app.listen(...args) // 直接交给node原生的http模块处理
    }
}

module.exports = Middleware

测试 (同一目录下新建一个demo.js文件)

const Demo = require("./ware")

const app = new Demo()

app.use(async (ctx, next) => {
    await next()
    console.log(`${ctx.method} ${ctx.url}`)
})

app.use(async ctx => {
    ctx.res.end("hello world")
})

app.listen(5000, () => {
    console.log("http://localhost:5000")
})

> cmd运行 node demo.js 浏览器访问 http://localhost:5000 => hello world

posted @ 2020-08-10 18:20  demo_you  阅读(211)  评论(0编辑  收藏  举报