# Koa框架
# 1、基本用法
- 需先建对象与express不同:const app = new Koa()
- ctx(context)封装了res和req
- 返回使用ctx.body,可使用async和await
- ctx.body最终会转换为res.end
- 执行时机:洋葱模型的最外层,第一个中间件promise有结果后,将ctx.body转换为res.end()
- 异步使用时,不可放置异步函数中,需放置在await的promise中
- 也可直接使用ctx.res和ctx.req
- 常用属性:req、res、request、response
- 返回使用ctx.body,可使用async和await
- ctx属性:
const Koa = require('koa') // ⼀个类
const app = new Koa()
// 中间件,可以使⽤async await
app.use(async function(ctx){
// throw new Error('出错了')
// ctx封装了req和res的功能,这⾥相当于res.end
// 1、有输出,ctx.body在promise结果后
ctx.body = await Promise.resolve('zhuwa')
// 2、无输出
setTimeout(()=>{
ctx.body = 'zhuwa'
}, 3000)
})
// 错误事件
app.on('error', function(err, ctx) {
console.log(err)
ctx.res.end(`500 错误`)
})
app.listen(3002, () => {
console.log('koa server started at 3002')
})
// req request res response的区别
app.use(async function(ctx){
console.log(ctx.req.path) // 原⽣的req
console.log(ctx.request.path) // koa封装的request
console.log(ctx.request.req.path)
console.log(ctx.path) // 代理到request
ctx.body = await Promise.resolve('zhuwa') // ctx封装了req和res的功能,这⾥相当于res.end
//ctx.throw(401, "err msg");
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 2、中间件
使用app.use,回调函数只能传一个,多个需多次注册
异步流程会等待后续中间件有结果时返回
app.use(async function(ctx, next) {
console.log('1-1')
await next()
console.log('1-2')
})
app.use(async function(ctx, next) {
console.log('2-1')
await next()
console.log('2-2')
})
app.use(async function(ctx, next) {
console.log('3-1')
await next()
console.log('3-2')
})
//输出:1-1、2-1、3-1、3-2、2-2、1-2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 手写koa-compose
核心代码参考:koa-compose/index.js
/**
* 函数:compose
* @params middleware: Function[]
* @return function fnCompose
*
* 函数:fnCompose
* 作用:执⾏第0个,并传⼊ctx和⼀个next函数,让第0个⾃⼰决定何时执⾏第1个
* @params ctx next函数
* @return promise
* function fnCompose(ctx, next){ return promise }
*
* 函数:next
* 作用:执⾏第i个中间件,并⽤promise包裹起来
* @params i表示执⾏第⼏个
* @return 返回promise
* function next (i) {
* // 执⾏当前的fn,将ctx, next传⼊(绑定下⼀个i, i + 1)
* return promise
* }
*/
/**
* 注意的细节:边界情况处理
* 1、middleware必须是数组,其中每⼀项必须是fuction
* 2、最后⼀个中间件调⽤了next,i === length
* 3、⼀个中间件多次调⽤next会报错
* 异常捕获
*/
const handlers = [
async function(ctx, next) {
console.log('1-1')
await next()
console.log('1-2')
},
async function(ctx, next) {
console.log('2-1')
await next()
console.log('2-2')
},
async function(ctx, next) {
console.log('3-1')
await sleep(3)
console.log('3-2')
}
]
const compose = function(middleware) {
let index = -1 //next只能调用一次方法
return function(ctx, next) {
function disptach(i) {
if(index >= i) return Promise.reject('next multiples callback')
index = i
const fn = middleware[i]
if(i === middleware.length) fn = next
if(!fn) return Promise.resolve()
try {
return Promise.resolve(fn(ctx, disptach.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
return disptach(0)
}
}
const fnCompose = compose(handlers)
const context = {}
fnCompose(context).then(()=>{
console.log('通过ctx.body 设置 res.end')
}).catch(err => {
console.log('err', err)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 3、与express比较
框架 | express | koa |
---|---|---|
模型 | 线性模型 | 洋葱模型 |
表现 | 同步表现为洋葱模型,但遇到异步,即使使⽤了 await next()也不会等待,会继续向下执⾏,因为next()返回undefined | ⽆论同步,还是异步,都是洋葱模型,可以使⽤ await和next() 来等待下⼀个中间件执⾏完成 |
原理 | 使⽤回调, next(),返回undefined | next(),返回promise,会把中间件⽤promise包裹起来 |
错误 | 错误处理是在⼀个特殊签名的中间件中完成的,它必须被添加到链的后⾯才能捕获到前⾯的错误 | 可以使⽤try catch 捕获中间件错误 |
注意 | await next() 也不会等待下⼀个中间件异步完成 | 要使⽤await来等待异步操作;注意res.end的时机,等最外层的中间件(也就是第⼀个中间件)的promise有结果后,响应就会被返回 ctx.body ->res.end(ctx.body) |
# 4、中间件编写
//编写⼀个计算请求的时⻓的中间件
// koa 中间件
app.use(async function(ctx, next) {
console.time('serviceTime')
await next()
console.timeEnd('serviceTime')
})
// express 中间件
app.use(function(req, res, next) {
console.time('service')
next()
res.once('close', () => {
console.timeEnd('service')
});
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 5、错误处理
// 可以使⽤try catch 捕获中间件错误
// 在最顶层捕获未处理的错误
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
const status = err.status || 500;
ctx.status = status;
ctx.type = "html";
ctx.body = `
<b>${status}</b> ${err}
`;
// emmit
ctx.app.emit("error", err, ctx);
}
});
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.status = 400
ctx.body = `Uh-oh: ${err.message}`
console.log('Error handler:', err.message)
}
})
app.use(async (ctx) => {
if (ctx.query.greet !== 'world') {
throw new Error('can only greet "world"')
}
console.log('Sending response')
ctx.status = 200
ctx.body = `Hello ${ctx.query.greet} from Koa`
})
// 也可以使⽤总的错误事件来统⼀处理
// 错误事件
app.on('error', function(err) {
console.log(err)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 6、常用中间件
- koa-router:路由
- koa-static:静态资源
- koa-mounut:子路由
- koa-body:body解析
- koa-parameter:参数校验
//1、koa-router
const KoaRouter = require('koa-router')
router.get('/page', async ctx => {
ctx.body = await new Promise((resolve, reject) => {
resolve('ok')
})
})
// 注册中间件
app.use(router.routes())
// 添加前缀
const router = new Router({ prefix: '/user' })
// 重定向
router.get('/home', async ctx => {
ctx.redirect('http://baidu.com');
});
// 路由参数
router.get('/:id', async ctx => {
ctx.body = {
msg: 'index page',
params: ctx.params.id // 通过 ctx.params.id 获取到的是 1
};
});
router.get('/', async ctx => {
ctx.body = {
msg: 'index page',
query: ctx.query // ctx.query会获取url后⾯携带的参数对象
};
});
// allowedMethod
// 如果没有这⼀⾏, 当使⽤其他请求⽅式请求user时, 会提示404
// 如果有这⼀⾏, 当使⽤其他请求⽅式请求user时, 提示405 method not allowed
app.use(router.allowedMethod());
//2、koa-static
const KoaStatic = require('koa-static')
app.use(KoaStatic(path.resolve(__dirname, '../dist/assets')))
//3、koa-mounut
const KoaMount = require('koa-mount')
// 添加前缀
app.use(KoaMount('/assets', KoaStatic(path.resolve(__dirname, '../dist/assets'))))
//4、koa-body
const bodyparser = require('koa-body');
// 在注册此中间件之后, 会在 ctx 上下⽂注⼊ ctx.request.body 属性 ⽤于获取客户端传递来的数据
app.use(bodyparser());
router.post('/', async ctx => {
ctx.body = {
code: 200,
//body 可以获取form表单数据, json数据
msg: ctx.request.body
}
})
//5、koa-parameter
const parameter = require('koa-parameter');
parameter(app);
router.post('/', async ctx => {
//接收⼀个对象
ctx.verifyParams({
// 校验的简写形式
username: 'string',
password: { type: 'string', required: true } // 或者也可以这样
})
ctx.body = { code: 1 }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
← Express 框架 数据库 →