一、 在 koa 中經常使用的 app.use()
函數源碼以下git
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;
}
複製代碼
this.middleware = [fn,fn,fn...]
,爲一箇中間件函數數組,每次使用 app.use(middleware())
,都會將middleware
中間件添加進 this.middleware
數組中。github
在app.listen('3000')
中,會運行以下函數數組
listen(...args) {
debug("listen");
const server = http.createServer(this.callback());
return server.listen(...args);
}
callback() {
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);
}
複製代碼
首先咱們看下 http.createServer()
幹了啥事,其核心源碼以下bash
function Server(options, requestListener) {
if (!(this instanceof Server)) return new Server(options, requestListener);
if (typeof options === 'function') {
requestListener = options;
options = {};
} else if (options == null || typeof options === 'object') {
options = { ...options };
} else {
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
}
this[kIncomingMessage] = options.IncomingMessage || IncomingMessage;
this[kServerResponse] = options.ServerResponse || ServerResponse;
net.Server.call(this, { allowHalfOpen: true });
if (requestListener) {
this.on('request', requestListener);
}
.......
}
複製代碼
核心是 this.on('request', requestListener)
這句,意識是每次來新請求都會調用 requestListener
函數,也就是 koa 源碼中的 this.callback()
運行的結果。app
先來看一下 const fn = compose(this.middleware);
的結果,compose 是一個將 中間件函數數組 轉化成洋蔥式調用函數的工具函數,compose 核心源碼以下所示koa
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);
}
}
};
複製代碼
以 koa-bodyParser 中間件爲例,其源碼以下所示async
return async function bodyParser(ctx, next) {
if (ctx.request.body !== undefined) return await next();
if (ctx.disableBodyParser) return await next();
try {
const res = await parseBody(ctx);
ctx.request.body = "parsed" in res ? res.parsed : {};
if (ctx.request.rawBody === undefined) ctx.request.rawBody = res.raw;
} catch (err) {
if (onerror) {
onerror(err, ctx);
} else {
throw err;
}
}
await next();
};
複製代碼
若是 this.middleware = [bodyParser1, bodyParser2, bodyParser3]
,this.middleware
通過 compose
處理後結果返回以下。函數
Promise.resolve()
支持鏈式調用工具
function(context, next) {
return Promise.resolve(async function bodyParser1(context, next) {
...
await async function bodyParser2(context, next) {
...
await async function bodyParser3(context, next) {
...
await Promise.resolve()
}
}
})
}
複製代碼
每次有新請求,先調用 this.callback()
中的ui
(req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
複製代碼
再調用 通過 compose
處理後的中間件函數 fn
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);
}
複製代碼