本文分析了Koa框架的核心源码,看懂了compose方法,也就看懂了 Koa。前端
Node 主要用在开发 Web 应用,koa 是目前 node 里最流行的 web 框架。node
在 Node 开启一个 http 服务简直易如反掌,下面是官网 demo。web
const http = require("http");
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.end("Hello World\n");
});
const hostname = "127.0.0.1";
const port = 3000;
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
复制代码
http
模块,http
的createServer
方法建立了一个http.Server
的实例。server
监听3000
端口。createServer
里的函数实际是监听request
事件的回调,每当请求进来,监听函数就会执行。request
事件的监听函数,其函数接受两个参数,分别是req
和res
。其中 req 是一个可读流,res 是一个可写流。咱们经过 req 获取 http 请求的全部信息,同时将数据写入到 res 来对该请求做出响应。Koa 如何建立一个 server, 直接上个官网的例子编程
const Koa = require("koa");
const app = new Koa();
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set("X-Response-Time", `${ms}ms`);
});
// logger
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});
// response
app.use(async (ctx) => {
ctx.body = "Hello World";
});
app.listen(3000);
复制代码
中间件概念在编程中使用普遍, 无论是前端仍是后端, 在实际编程或者框架设计中都有使用到这种实用的模型。后端
基本上,Koa 全部的功能都是经过中间件实现的。数组
每一个中间件默认接受两个参数,第一个参数是Context
对象,第二个参数是next
函数。只要调用next
函数,就能够把执行权转交给下一个中间件。app
若是中间件内部没有调用next
函数,那么执行权就不会传递下去。框架
多个中间件会造成一个栈结构(middle stack),以“先进后出”(first-in-last-out)的顺序执行。整个过程就像,先是入栈,而后出栈的操做。koa
上面代码的执行顺序是:
请求 -> x-response-time 中间件 -> logger 中间件 -> response 中间件 -> logger 中间件 -> response-time 中间件 -> 响应async
阅读源码,化繁为简,咱们看看 koa 的中间件系统是如何实现的。
class Application extends Emitter {
constructor() {
super();
this.middleware = [];
},
use(fn) {
this.middleware.push(fn);
return this;
},
callback() {
const fn = compose(this.middleware);
return function(req, res) {
return fn(ctx);
};
},
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
}
复制代码
好了,精简结束,一不当心,去枝末节,最后只剩下不到 20 行代码。
这就是框架的核心,简化后的代码很是清晰,有点难以想象,但核心就是这么简单。
咱们先分析以上代码作了什么事。
middleware
数组来存储中间件。use
方法来注册一个中间件。其实就是简单的push
到自身的mideware
这个数组中。compose
方法,传入middleware
,应该是作了一些处理,返回了一个可执行的方法。你必定对中间的compose
方法很好奇,初此以外的代码都容易理解,惟独这个compose
不太知道究竟作了什么。其实,compose
就是整个中间件框架的核心。
compose
以外,代码已经很清楚的定义了
而compose
方法作了最为重要的一件事
compose,顾名思义,至关于把全部注册的中间件组合,它规定了中间件的执行顺序,返回一个“大”中间件,这个大中间件,就在 node 的 http 服务onRequest
事件的时候执行。
这是 compose
方法的源码,代码很简洁。
function compose(middleware) {
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];
if (i === middleware.length) fn = next;
if (!fn) return Promise.resolve();
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err);
}
}
};
}
复制代码
我试图去简化一下这个方法,但方法自己已经足够简洁。
经过compose
方法,能够看出next
指的就是下一个中间件,也就是咱们所编写的中间件方法的第二个参数,Koa 经过next()传递
实现中间件调用,结合 Promise
采用 递归调用 的通知机制,最终完成整个中间件栈的执行。
Koa 在中间件语法上采用了async
+await
语法来生成Promise
形式的程序控制流。
这种形式的控制流让整个 Koa 框架中间件的访问呈现出 自上而下的中间件流 + 自下而上的 response 数据流 的形式。
看懂了compose
方法,也就看懂了 Koa,你大概不肯相信如此简单怎么足以称为框架,但状况就是这样。
Koa 自己作的工做仅仅是定制了中间件的编写规范,而不内置任何中间件。一个 webrequest
会经过 Koa 的中间件栈,来动态完成response
的处理。
koa 是很是精简的框架,其中的精粹思想就是洋葱模型(中间件模型),koa 框架的中间件模型很是好用而且简洁,可是也有自身的缺陷,一旦中间件数组过于庞大,性能会有所降低,使用的时候要结合业务场景适当选择。