最近在学习koa2,但是自己陷入了瓶颈期。就是不知道学什么好,对未来有点迷茫。还好最近看到了知乎上的狼叔的文章
感到迷茫的话就一天阅读十个npm模块。
这让我坚定了阅读源码的信念
KOA2的基本组成
- application.js:框架入口;负责管理中间件,以及处理请求
- context.js:context对象的原型,代理request与response对象上的方法和属性
- request.js:request对象的原型,提供请求相关的方法和属性
- response.js:response对象的原型,提供响应相关的方法和属性
我们主要来看看application.js。
那我们就根据自己写koa的习惯来一步步看吧!
我们一般都会先new一个Koa实例
const app = new Koa();
这样我们就先来看看它的构造函数:
// 我们就讲讲一些简单的吧!
constructor() {
super();
this.proxy = false;
this.middleware = []; //中间件栈
this.subdomainOffset = 2;
this.env = process.env.NODE_ENV || 'development';
this.context = Object.create(context); //Object.create(context)创建一个对象,这个对象的原型指向context
this.request = Object.create(request);
this.response = Object.create(response);
// 所以上面三句话就是创造了三个对象
}
总而言之,它的构造函数只是初始化了一些我们接下来所必须的东西。
接着我们要开始写代码了,比如
app.use(async (ctx, next) => {
console.log(`1 start`);
await next();
console.log('1 end');
})
app.use(async (ctx, next) => {
console.log(`2 start`);
await next();
console.log('2 end');
})
app.use(async (ctx, next) => {
console.log(`3 start`);
await next();
console.log('3 end');
})
这里我们用到了use这个函数 那我们再来看看use吧
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will been removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/tree/v2.x#old-signature-middleware-v1x');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}
可以看到use函数其实也很简单。首先进行判断,如果传入的中间件不是函数则报错,然后判断如果是generator函数则将其转化为async await类函数。具体怎么转换的我们就先不讲啦。然后再进行统一的错误管理。最后,再把这个中间件推入中间件栈。并返回app实例,以便于我们进行链式操作。
之后我们会怎么写代码呢? 当然是向下面这样
app.listen(3000);
所有准备就绪,就可以开始监听端口啦。
然后这个listen函数 则是最重要的一点!
listen() {
debug('listen');
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}
进行统一的错误管理,并用原生的node.js方法创建一个http服务器。而这个服务器的回调函数则是由callback()创建,那么我们很容易想到,callback应该会返回一个方法。让我们来继续看看callback()
callback() {
const fn = compose(this.middleware);
if (!this.listeners('error').length) this.on('error', this.onerror);
return (req, res) => {
res.statusCode = 404;//如果接下来的中间件没有设置res 则默认为404状态
const ctx = this.createContext(req, res); //创建ctx, 把request和response以及其它东西封装进去
onFinished(res, ctx.onerror); // 当res结束或者报错时,调用onerror回调函数,这是一个npm库提供的方法
fn(ctx).then(() => respond(ctx)).catch(ctx.onerror);
};
}
果不其然,它返回了一个方法!
接下来有两行代码最为重要,是koa2实现洋葱式调用的关键!!!
就是这两行
const fn = compose(this.middleware);
fn(ctx).then(() => respond(ctx)).catch(ctx.onerror);
compose是由一个名为koa-compose的库,它可以将多个中间件组合成洋葱式调用的对象,它就是今天的重点了!我们点进去看看
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i] //fn只是一个函数声明,在下面调用
if (i === middleware.length) fn = next
// 这里的next其实没有用 只是用来处理i ===middleware.length的情况
// next永远是空 这个next和下面的next是不一样的
if (!fn) return Promise.resolve()
try {
// 因为fn()是一个async函数 返回一个promise对象 Promise.resolve()遇到promise对象的时候会原封不动的返回该对象
// context就是封装好的ctx对象, next是你写在use里面的next
// 执行fn()代码 就是执行自定的async函数 遇到内部await next()则会等待回调函数结束
// 而这个回调函数递归调用下一个middleware 碰到下一个middleware的await next()则会继续调用下一个
// 直到调用到最后一个 返回一个空的promise.resolve()对象 则先是最后一个middleware收到这个promise对象
// 就执行await()下面的函数 最后一个中间件执行完毕后
// 则会再到之前的中间件去执行
return Promise.resolve(fn(context, function next () {
/* 这个fn()就是
next就是它的回调函数
async (ctx,next) => {
console.log("1-start");
await next();
console.log("1-end");
}
注意这上面的函数是我们常写的函数 ctx就是context, next就是function next(){...}
* fn(context, function next () { //
return dispatch(i + 1)
})
*/
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
是不是很复杂鸭。。 不过没关系 大家可以跟着我的注释一步一步的看下来,相信大家肯定会懂的!。
注意我们在applicaiton.js调用 dispatch的时候并没有传入next
fn(ctx).then(() => respond(ctx)).catch(ctx.onerror);
所以,在dispatch里面的形参next一直为null!
好啦 这就是最基本的koa2实现洋葱式调用的方法啦 希望大家看得懂,看不懂的可以在留言区多多跟我讨论
网友评论