对比express
- 编程模型不同
- express的中间件是线型的
- Koa的中间件是U型的
- 对语言特性的使用不同
- express使用回调函数next()
- Koa1 使用generator语法
- Koa2 使用async/await语法
回顾一下之前的express的中间件模型
Koa的中间件模型
- Koa的中间件模型是一个U型
- 老王发一个请求->开始->f1->f2>f3>…..>结尾 (这是往下的过程)
- 从结尾又回去(u型的另外一边) 结尾->…..->f3->f2->f1->响应给老王
- 开始阶段
- 主要用来构造一个叫context的变量
- f1,f2,f3等中间件
- 访问开始阶段构造的context
- 调用await next() 进入下一个函数
工具安装
- npm i -g node-dev ts-node-dev || yarn global add node-dev ts-node-dev
- 如果要用ts,需要运行tsc —init 初始化tsconfig.
- 还需要安装 @types/koa
koa-demo-1
安装:
$ yarn add koa
$ yarn add —dev @types/koa
创建server.ts
$ tsc —init
server.ts
import Koa from 'koa'
const app = new Koa()
app.use(async(ctx)=>{
ctx.body = 'hello'
})
app.listen(3000)
$ ts-node-dev server.ts
打开localhost:3000
改进一下
import Koa from 'koa'
const app = new Koa()
app.use(async(ctx,next)=>{
ctx.body = ' hello'
await next()
ctx.body += '\n await next string'
})
app.use(async (ctx)=>{
ctx.body += ' world'
})
app.listen(3000)
从这个demo可以很好的看出这个u型的模型是怎么运行的
- 在第一个app.use()里 ctx.body写入 hello
- 在第二个app.use() 增加world
- 上面两步是u型往下的部分,第二个app.use()已经到底了。开始u型往上的部分
- 网上又开始执行第二个app.use(),再执行第一个app.use(),这时候ctx.body加上了string,这点我们可以从网页上推导到的,一五年awaite next string是出现在第二行的
- 所以await next()这个方法之后的js,会在u型往上的阶段执行
koa-demo-2
需求:记录一个请求从开始到写完hello world 所需要的时间
import Koa from 'koa'
const app = new Koa()
app.use(async (ctx, next) => {
// 空
await next();
const time = ctx.response.get('X-Response-Time'); //读取response header
console.log(`${ctx.url} - ${time}`); // 打印出来
});
app.use(async (ctx, next) => {
const start = Date.now(); //记录开始时间
await next();
const time = Date.now() - start; //记录总耗时 = 结束时间 - start
ctx.set('X-Response-Time', `${time}ms`); //写到header里
});
app.use(async ctx => {
ctx.body = 'Hello World';
// await next() 最后一个中间件,可以不写await next()
});
app.listen(3000)
//记录这个请求,从开始到写完hello world整个过程的用时
// 代码行数
- 开始fn1什么都不做,next了 // 6
- fn2 记录开始时间 // 12
- fn3往ctx.body写入hello world // 18
- 到底了,开始从下往上走
- fn2 记录总耗时,并写到response header里 // 14-15
- fn1 读取response header , 然后打印出来 // 8-9
从代码行数也可以看出来是一个先从上往下,到底部再从下往上的一个过程。
koa2的代码,一开始是看不出来的,必须要大脑里有这个u型模型,带着这个u去看,才能看出来。
用u型的模型去做这种需求,是比较好做的,那么用线型的express呢?
比较麻烦。
要在fn1记录开始时间,然后在最后插入一个fn4来计算时间,还要保证fn2和fn3的运行时间必须要在它们之间。
做起来很别扭
洋葱模型
看回koa-demo-2的代码,看一下它的运行顺序
这个运行顺序,有人把它描述为洋葱模型
怎么理解呢:
- 你写f1的时候,这个f1对应的是最外层的洋葱皮,f2是第二层洋葱皮,f3第三层,f4第四层,一直到f8就是最里层。
- 看图中的request,粗黑色箭头和response
- 每次Request过来的时候,就会先经过洋葱的最外层f1,然后f2 …最后到最里层f8,最后到达洋葱的中心
- 然后从洋葱的中心穿出去,又经过f8,f7,f6…..到f2,f1,最后把response发出去
- 所以是request 贯穿了洋葱 变成response
- 但是这个图不是Koa的原型图,实际上这个图是Python Web框架Pylons的框架图(想了解可以点击链接)
但是我觉得u型图更好理解一点。
await next()
app.use(async(ctx,next)=>{
const start = Date.now()
await next() //等待 下一个中间件执行
const time = Date.now - start
ctx.set(`X-Request-Time`, `${time}ms`)
})
- next() 表示进入下一个函数
- 下一个函数会返回一个Promise对象 ,P
- 当前的函数也会返回一个promise,在哪里返回的呢,只要你写了async,他就会返回一个promise。
- 下一个函数所有代码执行完毕后,将p设置为成功
- await会等待P成功后,再回头执行剩余的代码 (4、5行)
await next() 改成promise写法
app.use(async(ctx,next)=>{
const start = Date.now()
return next().then(()=>{
const time = Date.now - start
ctx.set(`X-Request-Time`, `${time}ms`)
}) //等待 下一个中间件执行
})
如上面所提到的,next().then会返回一个promise对象,所以next.then之后,就是下一个函数执行成功了,在执行then里面的代码。
return是必须要写的,因为每一个函数都要返回一个promise对象才行(当前的函数就是上个函数的下一个函数,下一个函数都要返回一个promise对象)
因为当前返回了一个promise对象之后,上一个函数才会知道“你成功了,我要执行了”。
- 一定要写return,因为中间件必返回Promise对象
- 错误处理在这里有点反模式,用app.on(‘error’)更方便(因为有可能你不知道下一个中间件做了什么,不知道的话是很难捕获错误的)