「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
手撕koa,从零掌握koa的实现原理(7)
往期更文
前言
- 回顾昨天的内容,我们了解了
koa
中间件的执行顺序,到此为止,我们的koa
实现到了这一步。koa/koa/lib/application.js
const http = require('http') const EventEmitter = require('events') const context = require('./context'); const request = require('./request'); const response = require('./response'); class Application extends EventEmitter { constructor(){ super() this.context = Object.create(context) this.request = Object.create(request) this.response = Object.create(response) } createContext(req, res){ // req, res 是原生的 request 和 response 是我们自己扩展 const ctx = Object.create(this.context) const request = Object.create(this.request) const response = Object.create(this.response) ctx.request = request ctx.request.req = ctx.req = req ctx.response = response; ctx.response.res = ctx.res = res; return ctx } handleRequest = (req, res) => { const ctx = this.createContext(req, res) } listen(){ const server = http.createServer(this.handleRequest) server.listen(...arguments) } } module.exports = Application
app.use()方法实现
koa
内部会维护一个中间件的栈,app.use()
的作用就是把中间件添加到这个栈中。因此我们需要在consturctor
中声明一个数组,用来存放中间件函数。koa/koa/lib/application.js
const http = require('http') const EventEmitter = require('events') const context = require('./context'); const request = require('./request'); const response = require('./response'); class Application extends EventEmitter { constructor(){ super() this.context = Object.create(context) this.request = Object.create(request) this.response = Object.create(response) this.middlewares = [] } createContext(req, res){ // req, res 是原生的 request 和 response 是我们自己扩展 const ctx = Object.create(this.context) const request = Object.create(this.request) const response = Object.create(this.response) ctx.request = request ctx.request.req = ctx.req = req ctx.response = response; ctx.response.res = ctx.res = res; return ctx } use(middleware){ this.middlewares.push(middleware) } handleRequest = (req, res) => { const ctx = this.createContext(req, res) } listen(){ const server = http.createServer(this.handleRequest) server.listen(...arguments) } } module.exports = Application
实现中间件调用compose函数
handleRequest
补充实现- 每当我们请求来临的时候,在我们的
handleRequest
中。 第一步是创建我们的上下文。 第二步是按照顺序执行我们的中间件。 我们默认给状态码404
,如果ctx.body
没有值就返回Not Found
koa/koa/lib/application.js
handleRequest = (req, res) => { const ctx = this.createContext(req, res) res.statusCode = 404 this.compose(ctx).then(() => { let body = ctx.body if(body){ res.end(body) }else{ res.end('Not Found') } }).catch(e => { this.emit('error', e) }) }
- compose实现
index
记录上次调用的中间件的索引,初始状态默认是-1
。i
代表本次调用的中间件的索引, 如果i <= index
代表本次中间件已经被调用过了,抛出异常,只能被调用一次。index = i
更改index
if(this.middlewares.length === i) return Promise.resolve()
如果本次是最后调用的中间件那么next
函数会返回一个空的成功的Promise
,这也是最后一个中间件的next
不用调用的原因。let middleware = this.middlewares[i]
取出本次的middleware
Promise.resolve(middleware(ctx, () => dispatch(i+1))
执行本次的中间件函数,可以看出每次中间件函数的返回值都被Promise.resolve
做了处理,并且把下次的中间件作为next
函数传进去,如果在调用中间出错那么本次中间件就会返回失败的Promise
,后续的中间件函数也会被中断。- 默认调用第
0
个中间件,并且返回。
koa/koa/lib/application.js
compose(ctx){ let index = -1 const dispatch = (i) => { if(i <= index){ return Promise.reject(new Error('next() called multiple times')) } index = i if(this.middlewares.length === i) return Promise.resolve() let middleware = this.middlewares[i] try{ return Promise.resolve(middleware(ctx, () => dispatch(i+1))) //这里的() => dispatch(i+1) 代表我们的next函数也就是下一次中间件的执行 }catch(e){ return Promise.reject(e) } } return dispatch(0) }
完整的Koa应用
koa/koa/lib/application.js
const http = require('http') const EventEmitter = require('events') const context = require('./context'); const request = require('./request'); const response = require('./response'); class Application extends EventEmitter { constructor(){ super() this.context = Object.create(context) this.request = Object.create(request) this.response = Object.create(response) this.middlewares = [] } createContext(req, res){ // req, res 是原生的 request 和 response 是我们自己扩展 const ctx = Object.create(this.context) const request = Object.create(this.request) const response = Object.create(this.response) ctx.request = request ctx.request.req = ctx.req = req ctx.response = response; ctx.response.res = ctx.res = res; return ctx } use(middleware){ this.middlewares.push(middleware) } compose(ctx){ let index = -1 const dispatch = (i) => { if(i <= index){ return Promise.reject(new Error('next() called multiple times')) } index = i if(this.middlewares.length === i) return Promise.resolve() let middleware = this.middlewares[i] try{ return Promise.resolve(middleware(ctx, () => dispatch(i+1))) //这里的() => dispatch(i+1) 代表我们的next函数也就是下一次中间件的执行 }catch(e){ return Promise.reject(e) } } } handleRequest = (req, res) => { const ctx = this.createContext(req, res) res.statusCode = 404 this.compose(ctx).then(() => { let body = ctx.body if(body){ res.end(body) }else{ res.end('Not Found') } }).catch(e => { this.emit('error', e) }) } listen(){ const server = http.createServer(this.handleRequest) server.listen(...arguments) } } module.exports = Application
最后
我们的koa
已经实现了,拿我们的koa
去跑一下代码,测试代码完美通过。
再换个测试用例,我们浏览器访问也是正确的。
koa/koa/src/server.js
const Koa = require('../koa')
const app = new Koa()
app.use((ctx) => {
if (ctx.path === '/get') {
console.log(ctx.query);
ctx.body = "Hello World get"
}else if(ctx.path === '/post'){
ctx.body = "Hello World post"
}
})
app.listen(3000, () => {
console.log('server start 3000')
})
下期预告
下篇内容介绍一下koa
的中间件用法,手写几个中间件的实现。