const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
一个简单的koa程序,我们看看它做了什么。
首先,惯例,打开koa的package.json,找到入口文件
"main": "lib/application.js",
application.js
提供了koa的构造函数,基本是对http模块的封装。通过use函数收集中间件。
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}
use将中间件存放在middleware数组。
callback() { //createServer的回调函数
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
可以看到核心就是compose(this.middleware)。
function compose (middleware) {
return function (context, next) {
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] //获取中间件
//fnMiddleware(ctx)没有传next参数,所以中间件执行完后fn为undefined
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
//执行中间件。当用户执行next的时候,就是执行下一个中间件
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
再来回顾下koa的中间件
let app = new Koa();
app.use(async (ctx, next) =>{
console.log(1);
await next();
console.log(4);
});
app.use(async (ctx, next) => {
console.log(2);
await next();
console.log(3);
});
执行顺序是1,2,3,4 这是为什么呢。
1、执行1,废话。
2、next()。执行下一个中间件。打印2。
3、next()。后面没有中间件,直接打印3。
4、第二个中间件执行完毕。打印4。
这就是所谓的剥洋葱模型了。
再看看中间件是怎么被驱动的。
1、起始调用,fnMiddleware(ctx)。fnMiddleware是下面的函数
function (context, next){
return dispatch(0)
}
2、dispatch(0) 取出第一个中间件fn,并执行fn(context, dispatch.bind(null, i + 1)))。回顾下中间件
async (ctx, next) =>{
console.log(1);
await next();
console.log(4);
}
next被赋值为dispatch(1)
3、函数里执行next(),即dispatch(1)。dispatch(1)就是执行下面函数
async (ctx, next) => {
console.log(2);
await next();
console.log(3);
}
同理next被赋值为dispatch(2)
4、2等于中间件数组长度,next 变成了Promise.resolve().停止了中间件的遍历
//fnMiddleware(ctx)没有传next参数,所以中间件执行完后fn为undefined
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
context.js
这就是我们常用ctx。这个文件主要是做了代理的事。
delegate(proto, 'request')
.method('acceptsLanguages')
.access('idempotent')
request.js/response.js
对原生res,req进行了封装。