2023.27 koa源码-编写miniKoa

大家好,我是wo不是黄蓉,今年学习目标从源码共读开始,希望能跟着若川大佬学习源码的思路学到更多的东西。

可以以通过new关键字创建,说明是个构造函数,也可以用es6class来实现,内置uselisten方法,需要构造函数支持这两个方法

javascript 复制代码
//创建koa对象,内置use和listen方法
class Koa {
    constructor() {}
    use() {
        console.log('use')
    }
    listen() {
        console.log('listen')
    }
}
module.exports = Koa
​

创建好后,执行index.cjs打印

perl 复制代码
use
use
listen

index.cjs 测试文件内容

javascript 复制代码
// const Koa = require('koa')
const Koa = require('./koa/index.cjs')
const app = new Koa()
​
//默认中间件函数是个异步函数,如果是同步的会怎么样
app.use((ctx, next) => {
    // app.emit('sayHello', 'hello world')
    console.log('1')
    next()
    console.log('2')
    ctx.body = 'Hello World'
})
​
app.use((ctx) => {
    console.log('3')
    // ctx.body = 'hello'
})
// app.on('sayHello', (args) => {
//  console.log(111, args)
// })
app.listen(3000)
​

实现listen方法。创建一个服务并且启动

javascript 复制代码
//创建服务  
listen() {
        const server = http.createServer(this.requestListener)
        server.listen(3001, '0.0.0.0', () => {
            console.log('server is running on :', 'http://localhost:3001')
        })
    }
//请求回调函数,当发起请求时会被调用
    requestListener(req, res) {
        //当请求时返回hello world,请求http://localhost:3001页面会打印出来hello world
        res.end('hello world')
    }

处理中间件

javascript 复制代码
    use(middleware) {
        //校验middleware是否是个函数
        if (!Utils.isFunction(middleware)) {
            throw new TypeError('middleware 必须是个函数!')
        }
        this.middleware.push(middleware)
    }

listen完请求时requestListener 方法中this丢失问题

用箭头函数,使用function会形成一个自己的作用域会造成this丢失,导致调不到this.handleRequest方法

kotlin 复制代码
    //请求监听函数
    requestListener() {
        //处理请求监听函数,也就是compose函数
        const fn = compose(this.middleware)
​
        //没有触发就调用了handleRequest方法,使用箭头函数,将this指向父级作用域
        const handleRequest = (req, res) => {
            const ctx = {
                request: req,
                response: res
            }
            return this.handleRequest(ctx, fn)
        }
​
        return handleRequest
    }

handleRequest方法,处理请求函数和处理响应,执行fn方法就是触发compose方法实现挨个调用中间件函数

scss 复制代码
    //请求处理函数
    handleRequest(ctx, fn) {
        //根据上下文执行fn
        //第一次执行第0个监听函数
        //执行compose函数,这边是个函数,不然第一次不会执行,也不会按照顺序执行compose返回结果是个promise
        fn(ctx)
    }

compose方法,我是放在koa对象外面的,也可以当成一个工具类方法

javascript 复制代码
//中间件挨个调用函数
function compose(middlewares) {
    return function (context, next) {
        //第一次执行fn(0)
        return dispatch(0)
        function dispatch(i) {
            //只能执行第一个函数
            let middlewareFn = middlewares[i]
            //当i和中间件长度相等时退出,可执行的函数为空
            if (i === middlewares.length) middlewareFn = next
            //不判断的话会循环执行,i一直在++,i终止的条件就是中间件函数执行完毕了
            if (!middlewareFn) return Promise.resolve()
            //返回一个函数异步函数,dispatch.bind 绑定参数,可以在调用函数时,预先传入一些参数,
            return Promise.resolve(middlewareFn(context, dispatch.bind(null, i + 1)))
        }
    }
}

处理响应

koa是通过getter/ setter来实现代理的,只是对原生的request,response做了封装。再将requestresponse对象都封装在context中,通过ctx.body返回响应内容,但是response上本上没有body属性,因此需要通过自定义封装来扩展body属性。

源码中为了方便代理,引入了delegates库,通过delegate实现对responserequest的代理。

response.js 封装res.end用来设置body

kotlin 复制代码
module.exports = {
    get body() {
        return this._body
    },
    set body(val) {
        this._body = val
        //原生是通过res.end返回响应的
        this.res.end(val)
    }
}

context.js 通过response来代理context

kotlin 复制代码
module.exports = {
    get body() {
        this.response.body
    },
    set body(val) {
        this.response.body = val
    }
}

设置上下文,将requestresponse设置到conetxt

ini 复制代码
    //创建上下文
    createContext(req, res) {
        //将原有的方法做转换
        const context = Object.create(this.context)
        const request = (context.request = Object.create(this.request))
        const response = (context.response = Object.create(this.response))
        context.app = request.app = response.app = this
        context.req = request.req = response.req = req
        context.res = request.res = response.res = res
        request.ctx = response.ctx = context
        request.response = response
        response.request = request
        context.originalUrl = request.originalUrl = req.url
        context.state = {}
        return context
    }

request,response,context关联到koa对象上

javascript 复制代码
const context = require('./context.cjs')
const request = require('./request.cjs')
const response = require('./response.cjs')
​
​
    constructor() {
        //保存中间件
        this.middleware = []
        this.context = Object.create(context)
        this.request = Object.create(request)
        this.response = Object.create(response)
    }

重启项目

这样一个简单的koa实现就好了,源码中还有一些错误处理的地方,属于优化部分,整体流程大概就是这样的。

源码地址

小结

整体代码不是很难,但是可以给我们提供一种代码编程思路,当你想对原有的东西进行扩展api时就可以使用setter/getter代理来实现新属性的代理,这样不仅不会影响原有的东西,而且还能更灵活方便的处理原有的api操作。

相关推荐
weixin_382395233 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__3 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.4 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~5 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华6 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子8 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅8 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni8 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学10 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端