koa2.0基于async await / koa1.0基于generator
koa 的大致流程,文件结构划分很简单,只有四个文件
- application
- context
- request
- response
测试用例
// 引入koa主要是帮我们封装了http服务
// 中间件机制、错误处理机制
// ctx context上下文
// 原生的(req、res)
// koa封装的request、response(有些原生对象上不存在的方法)
const app = require('./koa');
app.use((ctx)=>{
console.log(ctx.req.url)// 原生node的req对象
console.log(ctx.request.req.url)// 原生node的req对象
//---------------------------
console.log(ctx.request.url)// koa包装的request对象
console.log(ctx.url)// koa包装的request对象
})
app.listen(3000);
application封装创建应用程序函数
let context = require('./context')
let request = require('./request')
let response = require('./response')
let app = require('http');
class Application {
constructor(){
this.context=Object.create(context);
this.request=Object.create(request);
this.response=Object.create(response);
}
use(fn){// 不考虑多个中间件的情况,文末会做详细介绍
this.fn=fn
}
createContext(req,res){
let ctx=this.context;
// koa自己的request、response挂载
ctx.request=this.request;
ctx.response=this.response;
// 原生的req、res挂载
ctx.request.req=ctx.req=req;
ctx.response.res=ctx.res=res;
return ctx;
}
handleRequest(req,res){
let ctx = this.createContext(req,res);
this.fn(ctx)
}
listen(){
app.createServer(handleRequest.bind(this)).listen(...arguments)
}
}
context对象,拓展request、response
- context.js
使用defineGetter;拦截request/response中的方法,使context中能拿到request/response中的属性
class Delegator{
constructor(proto, target){
this.proto = proto;
this.target = target;
}
getters(name) {
let { proto, target } = this;
proto.__defineGetter__(name, function(){
return this[target][name];
});
return this;
}
setters(name) {
let { proto, target } = this;
proto.__defineSetter__(name, function(val){
return this[target][name] = val;
});
return this;
}
}
let proto = {}
new Delegator(proto, 'request')
.getters('url')
.getters('path')
new Delegator(proto, 'response')
.getters('body')
.setters('body')
module.exports = proto
- request.js
利用上文中 ctx.request.req=ctx.req=req;能拿到this.req给request对象添加属性
let parse = require('parseurl');
let request = {
get url(){
return this.req.url;
},
get path(){
return parse(this.req).pathname;
}
}
- response.js
利用上文中 ctx.response.res=ctx.res=res;能拿到this.res给response对象添加属性
let response = {
set body(value){
this.res.statusCode = 200;
this._body = value;
},
get body(){
return this._body;
}
}
中间件
- 洋葱模型,调用next会执行下一个use函数
const app = require('./koa');
app.use((ctx,next)=>{
console.log(1)
next()
console.log(2)
})
app.use((ctx,next)=>{
console.log(3)
next()
console.log(4)
})
app.listen(3000);
// 1-3-4-2
koa会把所有中间件进行组合 组合成了一个大的promise,只要从开头走向结束就算完成了
koa的中间件必须增加await next(),或者return next() 否则异步逻辑可能出错
- application如下扩展
this.middleware=[]
use(middleware){
this.middleware.push(middleware)
}
compose(ctx){// 组合函数,将功能组合在一起依次执行
function dispatch(i){
if(this.middleware.length===i){
return Promise.resolve()
}
let middleware= this.middleware[i]
return new Promise(middleware(ctx,()=>dispatch(i+1)))
}
return dispatch(0)// 要从第一个中间件开始执行
}
handleRequest(req,res){
const ctx=this.createContext(req,res);// 将req,res统一放到ctx上
res.statusCode=404;//默认设置返回码404
this.compose(ctx).then(()=>{
let body = ctx.body;
if(body){
res.end(body)//返回最终结果响应给用户
}else{
res.end('Not Found')
}
})
}
错误机制
compose会捕捉中间件中的错误
compose(ctx){
function dispatch(i){
if(this.middleware.length===i){
return Promise.resolve()
}
let middleware= this.middleware[i]
try{
return new Promise(middleware(ctx,()=>dispatch(i+1)))
}catch(err){
return Promise.reject(err)
}
}
return dispatch(0)// 要从第一个中间件开始执行
}
handleRequest(req, res) {
...
const onerror = err => ctx.onerror(err);//如果中间件中有报错就会触发ctx.onerror
// 通过 catch 调用 context 中的错误处理
this.compose(ctx, this.middlewares).then(...).catch(onerror)
}
context中的错误对象
let proto = {
onerror(err) {
if (null == err) return;
this.app.emit('error', err, this);// event模块的emit会触发app.on('error',()=>{ //错误处理 })
const { res } = this;
if (typeof res.getHeaderNames === 'function') {
res.getHeaderNames().forEach(name => res.removeHeader(name));
}
// respond
this.type = 'text/plain;charset=utf-8';
this.status = err.status;
res.end(err.message);
}
}