项目这段时间在使用koa,对立面的ctx以及异步中间件的执行很感兴趣,所以就索性实现了一下
- 需要对了解js里面this的指向
- 需要对promsie有一个清晰的认知
对koa ctx上面的属性做一个系统的人知
ctx.req.request.url // ctx.req = req
ctx.request.req.url // ctx.request.req = req;
ctx.request.url //ctx.request koa自己封装的
ctx.url // ctx代理了ctx.request
复制代码
对appliction文件的封装
const http = require("http");
const context = require("./context");
const request = require("./request");
const response = require("./response");
class Mkoa {
constructor() {
this.middlewares = [];
this.context = context;
this.request = request;
this.response = response;
}
use(cb) {
this.middlewares.push(cb); // 保存所有的中间件
}
createContext(req, res) {
let ctx = Object.create(this.context); // ctx可以拿到全局的context上的属性,并且修改ctx的时候不会影响到context
// 上下文上挂载请求 ctx.request 指向外部的request文件 方便扩展
ctx.request = Object.create(this.request);
ctx.req = ctx.request.req = req;
// ctx上挂载响应
ctx.response = Object.create(this.response);
ctx.res = ctx.response.res = res;
return ctx;
}
// 返回一个promise 保证是在中间件执行完毕
componse(ctx, middlewares) {
function dispatch(index) {
if (index === middlewares.length) {
return Promise.resolve();
}
const middleware = middlewares[index];
return Promise.resolve(
middleware(ctx, () => {
return dispatch(index + 1);
})
);
}
return dispatch(0);
}
hanleRequest(req, res) {
let ctx = this.createContext(req, res); // 基于req res 创造ctx
let composeMiddleware = this.componse(ctx, this.middlewares); // componse 组合执行内部的中间件,返回一个promise 保证是所有的中间件都是按照顺序执行的
// 所有的中间件都执行完毕过后,判断body上是否有值,再根据情况进行返回
// 判断body的类型 可以设置不同的请求头 文件 字符串 流
composeMiddleware.then(() => {
let body = ctx.body;
if (typeof body === "undefined") {
res.end(`not found`);
} else if (typeof body === "string") {
res.end(body);
}
});
}
listen(...arg) {
let app = http.createServer(this.hanleRequest.bind(this));
app.listen(...arg);
}
}
module.exports = Mkoa;
复制代码
对request文件的封装
let url = require('url');
const request = {
// 这个方法的调用是采用的 ctx.request.url => ctx.request.url.call(ctx.request)
// this指的就是 ctx.request
// ctx.req = ctx.request.req = req
get url(){
return this.req.url
},
get path(){
return url.parse(this.req.url).pathname
}
}
module.exports = request
/*
函数执行的时候 fn(a,b) 会转换为标准的执行方式 fn.call(context,a,b)
obj.child.method(p1, p2) 等价于 obj.child.method.call(obj.child, p1, p2);
var obj = {
foo: function(){
console.log(this)
}
}
var bar = obj.foo
obj.foo() // 转换为 obj.foo.call(obj),this 就是 obj
bar()
转换为 bar.call()
由于没有传 context
所以 this 就是 undefined
最后浏览器给你一个默认的 this —— window 对象
function fn (){ console.log(this) }
var arr = [fn, fn2]
arr[0]() // 这里面的 this 又是什么呢?
*/
复制代码
对response文件的封装
const response = {
set body(value){
this.res.statusCode = 200;
this._body = value // 保存value值
},
get body(){
this.res.statusCode = 404;
return this._body
}
}
module.exports = response
复制代码
context封装
// 直接访问ctx上的属性
const context = {
}
// koa 源码里面的 delegate
function defineGetter(property,name){
// context[name] 返回的是 context[property][name]
context.__defineGetter__(name,function(){
return this[property][name]
})
}
// ctx.body => ctx.body.call(ctx)
function defineSetter(property,name){
// context[name] 返回的是 context[property][name]
context.__defineSetter__(name,function(value){
return this[property][name] = value
})
}
// koa 内部有一个delegate的方法 我们只做简易实现
defineGetter('request','url'); //完整的访问链 ctx.url => ctx.request.url => ctx.request.req.url
defineGetter('request','path');
defineGetter('response','body')
defineSetter('response','body')
module.exports = context
复制代码
编写一个运行实例
let koa = require("./ckoa/application");
let app = new koa();
function logger() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("已异步顺序的问题");
resolve();
}, 2000);
});
}
/**
* 如果一个中间件里面存在异步的情况 我们需要使用await next() 保证下一个中间件执行完毕,才执行当前中间件后续的代码
* koa 中间件在没有异步的情况下是和express是一样的执行顺序 没有什么区别
*/
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(2);
});
app.use(async (ctx, next) => {
console.log(3);
await logger()
next();
console.log(4);
});
app.use((ctx, next) => {
console.log(5);
next();
console.log(6);
});
app.listen(3001);
复制代码
文件分析
/*
context request response 之间的关系
ctx.request = Object.create(我们自定义扩展的request)
ctx.request 就可以通过原型链访问到request上定义的属性,
可以看koa源码里面的request文件,
基本上都是 this.req.**** this.req.url this.req.header
都是表示的原生的req
ctx.request.url 首先ctx.request 上没有url这个属性,
需要通过原型链去进行查找 原型上的this指向的就是实例属性。
或者ctx.request.url() => ctx.request.url.call(ctx.request) => this指向的就是 ctx.request
ctx.request.req.url 其实就是 req.url 因为 ctx.req = ctx.request.req = req;
ctx.url 首先ctx上并没有 url 一样是原型链进行查找,
会找到context 文件里面去,因为ctx的原型指向的是 context这个文件,
但是context这个文件里面又进一步做了一次代理。
ctx.url通过delegate过后访问的其实就是 ctx.request.url 就回到了最初的时候 ctx.request.req.url
ctx.req = ctx.request.req = req 所以会有 ctx.req.*** ctx.request.req.** 就是访问的原生的req上的属性.
ctx.req.path ctx.request.req.path 是访问不了的
但是 ctx.request.path ctx.path 访问的path其实是用户自己封装的path,
在request文件里面,
ctx.request.path返回的是 url.parse(this.req.url).pathname,
里面的this就是ctx.request。
同理,ctx.path 访问的就是context里面的getter 其实还是 ctx.request.path
ctx.response = Object.create(response)
ctx.res = ctx.response.res = res
ctx.body => 访问的是context里面代理过后的,
其实设置的其实是 ctx.response.body => 这样访问的就是response里面的body,
这个时候里面的this 其实指的就是 ctx.response.body,
并且里面我们设置了 this.res.statusCode = 200;
其实设置的 ctx.response.res.statusCode = 200
*/
复制代码
componse串行中间件的时候遇到的问题
没有理解到为什么一定要return dispatch(index+1) 涉及到promise的嵌套问题。先去实现一个promise过后再来补充
Promise.resolve(middleware(ctx,()=>{
return dispatch(index+1)
}))
复制代码