手写Koa.js源码 (4)

上面的this.context其实就是来自context.js,所以我们先在Application构造函数里面添加这个变量:

// application.js const context = require("./context"); // 构造函数里面 constructor() { // 省略其他代码 this.context = context; }

然后再来看看context.js里面有啥,context.js的结构大概是这个样子:

const delegate = require("delegates"); module.exports = { inspect() {}, toJSON() {}, throw() {}, onerror() {}, }; const proto = module.exports; delegate(proto, "response") .method("set") .method("append") .access("message") .access("body"); delegate(proto, "request") .method("acceptsLanguages") .method("accepts") .access("querystring") .access("socket");

这段代码里面context导出的是一个对象proto,这个对象本身有一些方法,inspect,toJSON之类的。然后还有一堆delegate().method(),delegate().access()之类的。嗯,这个是干啥的呢?要知道这个的作用,我们需要去看delegates这个库:https://github.com/tj/node-delegates,这个库也是tj大神写的。一般使用是这样的:

delegate(proto, target).method("set");

这行代码的作用是,当你调用proto.set()方法时,其实是转发给了proto[target],实际调用的是proto[target].set()。所以就是proto代理了对target的访问。

那用在我们context.js里面是啥意思呢?比如这行代码:

delegate(proto, "response") .method("set");

这行代码的作用是,当你调用proto.set()时,实际去调用proto.response.set(),将proto换成ctx就是:当你调用ctx.set()时,实际调用的是ctx.response.set()。这么做的目的其实也是为了使用方便,可以少写一个response。而且ctx不仅仅代理response,还代理了request,所以你还可以通过ctx.accepts()这样来调用到ctx.request.accepts()。一个ctx就囊括了response和request,所以这里的context也是一个语法糖。因为我们前面已经踢了response和request这两个语法糖,context作为包装了这两个语法糖的语法糖,我们也一起踢掉吧。在Application的构造函数里面直接将this.context赋值为空对象:

// application.js constructor() { // 省略其他代码 this.context = {}; }

现在语法糖都踢掉了,整个Koa的结构就更清晰了,ctx上面也只有几个必须的变量:

ctx = { app, req, res }

context.js对应的源码看这里:https://github.com/koajs/koa/blob/master/lib/context.js

app.handleRequest

现在我们ctx和fn都构造好了,那我们处理请求其实就是调用fn,ctx是作为参数传给他的,所以app.handleRequest代码就可以写出来了:

// 处理具体请求 handleRequest(ctx, fnMiddleware) { const handleResponse = () => respond(ctx); // 调用中间件处理 // 所有处理完后就调用handleResponse返回请求 return fnMiddleware(ctx) .then(handleResponse) .catch((err) => { console.log("Somethis is wrong: ", err); }); }

我们看到compose库返回的fn虽然支持第二个参数用来收尾,但是Koa并没有用他,如果不传的话,所有中间件执行完返回的就是一个空的promise,所以可以用then接着他后面处理。后面要进行的处理就只有一个了,就是将处理结果返回给请求者的,这也就是respond需要做的。

app.handleRequest对应的源码看这里:

respond

respond是一个辅助方法,并不在Application类里面,他要做的就是将网络请求返回:

function respond(ctx) { const res = ctx.res; // 取出res对象 const body = ctx.body; // 取出body return res.end(body); // 用res返回body } 大功告成

现在我们可以用自己写的Koa替换官方的Koa来运行我们开头的例子了,不过logger这个中间件运行的时候会有点问题,因为他下面这行代码用到了语法糖:

console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);

这里的ctx.method和ctx.url在我们构建的ctx上并不存在,不过没关系,他不就是个req的语法糖嘛,我们从ctx.req上拿就行,所以上面这行代码改为:

console.log(`${ctx.req.method} ${ctx.req.url} - ${ms}ms`); 总结

通过一层一层的抽丝剥茧,我们成功拎出了Koa的代码骨架,自己写了一个迷你版的Koa。

这个迷你版代码已经上传GitHub,大家可以拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/KoaCore

最后我们再来总结下本文的要点吧:

Koa是Express原班人马写的一个新框架。

Koa使用了JS的新API,比如async和await。

Koa的架构和Express有很大区别。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxfgd.html