# 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属性:
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、中间件

使用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
  • 手写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

# 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

# 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

# 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