1. 早期没有koa之前代码是如何工作的?
早期没有koa
与expess
之前,所有的请求响应只能在http.createServer
中完成
const http = require('http')
const server = http.createServer((req, res)=>{
//早期在这里面判断 req.url(请求路径) 来实现返回不同的数据
if (req.url == "/") {
res.writeHead(200, { "Content-Type": "text/html" }) //返回请求状态码
res.end("返回根目录页面给浏览器")
} else if (req.url == "/about") {
res.writeHead(200, { "Content-Type": "text/html" });
res.end("返回/about目录页面给浏览器")
}
})
server.listen(3000,()=>{
console.log('监听端⼝3000') //服务器开启成功的回调函数
})
2. 有koa中间件后代码的工作方式
通过执行app.use(fn)
传入一个fn
函数作为参数,而这个函数就是一个中间件
const Koa = require('koa')
const app = new Koa() // 创建koa服务, koa内部是封装http实现的
app.use(async (ctx, next) => {
console.log(1)
await next()
console.log(4)
})
app.use(async (ctx, next) => {
console.log(2)
await next()
console.log(3)
})
app.listen(3000) // 监听服务
//执行结果: 1, 2, 3, 4
上面的意思是:浏览器访问一个请求过来会经过1, 2, 3, 4
这4个地方的不同处理,从而返回一个最终的数据给浏览器。 koa与http相比 意义在于更分层次,更有逻辑性的处理了浏览器的请求
koa中间件的执行过程类似一个切洋葱的过程,洋葱上一半由外到内
一层一层切开(执行)
,下一半由内到外
一层一层切开(执行)
, 所以我们称 koa中间件 模型为 “洋葱模型”。
3. 如何自己实现一个koa呢?
我们知道koa
是利用 洋葱模型原理 通过封装http
实现的,下面我们也来实现一个koa
吧!
const http = require('http')
class Koa {
constructor() {
this.middlewares = [] //存储 koa.use(fn) 传入的中间件函数 fn
}
//当执行 koa.listen(3000) 方法时创建一个 http 服务
listen(...args) {
const server = http.createServer(async (req, res) => {
const ctx = this.createContext(req, res) //1. 得到 ctx 封装好的请求相应参数
const fn = this.compose(this.middlewares) //2. 通过高阶函数原理将 ctx 传入中间件中
await fn(ctx) //3. 执行所有的中间件方法
res.end(ctx.body) //4. 最终给浏览器返回数据
})
server.listen(...args)
}
use(middleware) {
this.middlewares.push(middleware)
}
createContext(req, res) {
//选择性暴露 get set 方法 目的不让用户随意修改属性
const ctx = Object.create({
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
}
})
ctx.request = Object.create({
get url(){
return this.req.url
},
get method(){
return this.req.method.toLowerCase()
}
})
ctx.response = Object.create({
get body(){
return this._body
},
set body(val){
this._body = val
}
})
ctx.req = ctx.request.req = req
ctx.res = ctx.response.res = res
return ctx
}
//洋葱模型核心原理
compose(middlewares) {
return function (ctx) {
return dispatch(0)
function dispatch(i) {
let fn = middlewares[i]
if (!fn) {
return Promise.resolve()
}
// dispatch 方法返回 Promise 的原因是确保洋葱模型存在异步情况下可以使用 await语法 让洋葱模型依然按照顺序执行
return Promise.resolve(
fn(ctx, function next() {
//函数循环递归时:上一个函数要等到下一个函数执行完成才算执行完成 这就是洋葱模型的核心原理
return dispatch(i + 1)
})
)
}
}
}
}
//下面我们使用自己实现的 Koa
const app = new Koa() // 创建koa服务, koa内部是封装http实现的
app.use(async (ctx, next) => {
console.log(1)
await next()
console.log(4)
});
app.use(async (ctx, next) => {
console.log(2)
await delay() //测试异步后代码是否可以按照顺序执行
await next()
console.log(3)
});
function delay() {
return new Promise(reslove => {
setTimeout(() => {
console.log('我是中途的异步代码')
reslove()
}, 2000)
})
}
app.listen(3000, ()=>{
console.log('监听端⼝3000') //服务器开启成功的回调函数
})
运行结果如下: