学习koa源码,弄懂中间件和路由的实现原理

文章详细介绍了Koa框架如何通过上下文对象简化API操作,以及如何利用洋葱圈模型实现异步中间件的组合,包括同步和异步函数的组合方式。
摘要由CSDN通过智能技术生成

listen(…args) {

const server = http.createServer((req, res) => {

this.callback(req, res)

})

server.listen(…args)

}

}

module.exports = Moa

Context

koa 为了能够简化 API,引入了上下文 context 的概念,将原始的请求对象 req 和响应对象 _res_封装并挂载到了 context 上,并且设置了 getter 和 setter ,从而简化操作

// index.js

// …

// app.use((req, res) => {

//   res.writeHeader(200)

//   res.end(‘hello, Moa’)

// })

app.use(ctx => {

ctx.body = ‘cool moa’

})

// …

为了达到上面代码的效果,我们需要分装 3 个类,分别是 contextrequestresponse , 同时分别创建上述 3 个 js 文件,

// request.js

module.exports = {

get url() {

return this.req.url

}

get method() {

return this.req.method.toLowerCase()

}

}

// response.js

module.exports = {

get body() {

return this._body

}

set body(val) = {

this._body = val

}

}

// context.js

module.exports = {

get url() {

return this.request.url

}

get body() = {

return this.response.body

}

set body(val) {

this.response.body = val

}

get method() {

return this.request.method

}

}

接着我们需要给 Moa 这个类添加一个 createContext(req, res) 的方法, 并在 listen()方法中适当的地方挂载上:

// moa.js

const http = require(‘http’)

const context = require(‘./context’)

const request = require(‘./request’)

const response = require(‘./response’)

class Moa {

// …

listen(…args) {

const server = http.createServer((req, res) => {

// 创建上下文

const ctx = this.createContext(req, res)

this.callback(ctx)

// 响应

res.end(ctx.body)

})

server.listen(…args)

}

createContext(req, res) {

const ctx = Object.create(context)

ctx.request = Object.create(request)

ctx.response = Object.create(response)

ctx.req = ctx.request.req = req

ctx.res = ctx.response.res = res

}

}

中间件

Koa 中间键机制:Koa 中间件机制就是函数组合的概念,将一组需要顺序执行的函数复合为一个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制,是 Koa 源码中的精髓和难点。

洋葱圈模型

同步函数组合

假设有 3 个同步函数:

// compose_test.js

function fn1() {

console.log(‘fn1’)

console.log(‘fn1 end’)

}

function fn2() {

console.log(‘fn2’)

console.log(‘fn2 end’)

}

function fn3() {

console.log(‘fn3’)

console.log(‘fn3 end’)

}

我们如果想把三个函数组合成一个函数且按照顺序来执行,那通常的做法是这样的:

// compose_test.js

// …

fn3(fn2(fn1()))

执行 node compose_test.js 输出结果:

fn1

fn1 end

fn2

fn2 end

fn3

fn3 end

当然这不能叫做是函数组合,我们期望的应该是需要一个 compose() 方法来帮我们进行函数组合,按如下形式来编写代码:

// compose_test.js

// …

const middlewares = [fn1, fn2, fn3]

const finalFn = compose(middlewares)

finalFn()

让我们来实现一下 compose() 函数,

// compose_test.js

// …

const compose = (middlewares) => () => {

[first, …others] = middlewares

let ret = first()

others.forEach(fn => {

ret = fn(ret)

})

return ret

}

const middlewares = [fn1, fn2, fn3]

const finalFn = compose(middlewares)

finalFn()

可以看到我们最终得到了期望的输出结果:

fn1

fn1 end

fn2

fn2 end

fn3

fn3 end

异步函数组合

了解了同步的函数组合后,我们在中间件中的实际场景其实都是异步的,所以我们接着来研究下异步函数组合是如何进行的,首先我们改造一下刚才的同步函数,使他们变成异步函数,

// compose_test.js

async function fn1(next) {

console.log(‘fn1’)

next && await next()

console.log(‘fn1 end’)

}

async function fn2(next) {

console.log(‘fn2’)

next && await next()

console.log(‘fn2 end’)

}

async function fn3(next) {

console.log(‘fn3’)

next && await next()

console.log(‘fn3 end’)

}

//…

现在我们期望的输出结果是这样的:

fn1

fn2

fn3

fn3 end

fn2 end

fn1 end

同时我们希望编写代码的方式也不要改变,

// compose_test.js

// …

const middlewares = [fn1, fn2, fn3]

const finalFn = compose(middlewares)

finalFn()

所以我们只需要改造一下 compose() 函数,使他支持异步函数就即可:

// compose_test.js

// …

function compose(middlewares) {

return function () {

return dispatch(0)

function dispatch(i) {

let fn = middlewares[i]

if (!fn) {

return Promise.resolve()

}

return Promise.resolve(

fn(function next() {

return dispatch(i + 1)

})

)

}

}

}

const middlewares = [fn1, fn2, fn3]

const finalFn = compose(middlewares)

finalFn()

运行结果:

fn1

fn2

fn3

fn3 end

fn2 end

fn1 end

完美!!!

完善 Moa

我们直接把刚才的异步合成代码移植到 moa.js 中, 由于 koa 中还需要用到 ctx 字段,所以我们还要对 compose() 方法进行一些改造才能使用:

// moa.js

// …

class Moa {

// …

compose(middlewares) {

return function (ctx) {

总结

以上是字节二面的一些问题,面完之后其实挺后悔的,没有提前把各个知识点都复习到位。现在重新好好复习手上的面试大全资料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等),现在起闭关修炼半个月,争取早日上岸!!!

下面给大家分享下我的面试大全资料

  • 第一份是我的后端JAVA面试大全

image.png

后端JAVA面试大全

  • 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

  • 第三份是Spring全家桶资料

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

a_ 中还需要用到 ctx 字段,所以我们还要对 compose() 方法进行一些改造才能使用:

// moa.js

// …

class Moa {

// …

compose(middlewares) {

return function (ctx) {

总结

以上是字节二面的一些问题,面完之后其实挺后悔的,没有提前把各个知识点都复习到位。现在重新好好复习手上的面试大全资料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等),现在起闭关修炼半个月,争取早日上岸!!!

下面给大家分享下我的面试大全资料

  • 第一份是我的后端JAVA面试大全

[外链图片转存中…(img-YFHMAA7C-1714196484452)]

后端JAVA面试大全

  • 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理

[外链图片转存中…(img-IMMU81xj-1714196484452)]

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

  • 第三份是Spring全家桶资料

[外链图片转存中…(img-jeLQgcPO-1714196484453)]

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值