下面是application.js代碼結構,並不是完整的源碼,方便讀者閱讀node
class Application extends Emitter{
<!--初始化 new Koa()-->
constructor() {
this.proxy //若是爲 true,則解析 "Host" 的 header 域,並支持 X-Forwarded-Host
this.subdomainOffset //表示 .subdomains 所忽略的字符偏移量
this.env // 默認爲 NODE_ENV or "development"
this.middleware = [] // 中間件集合
this.context
this.request
this.response
this[util.inspect.custom] = this.inspect //將對象轉換爲字符串的方法,一般用於調試和錯誤輸出,這裏的用到node中util,this inspect調用 toJson方法中的only方法,返回對象
}
<!--listen方法,啓動服務-->
listen(...args) {
<!--實際調用node.js 中的http.createServer服務-->
const server = http.createServer(this.callback()) //返回http.server實例
return server.listen(...args)
<!--這裏面的args是端口號+回調函數,因此根據node官網api規定用於TCP鏈接-->
}
<!--調用中間件的use方法-->
use(fn){
<!--第一件事判斷是不是generator函數,若是是利用koa-conver(實際這裏面利用co插件的wrap方法)轉化成async await 函數-->
if (isGeneratorFunction(fn)) {
fn = convert(fn)
}
<!--第二件事,中間件存儲到定義的變量數組中-->
this.middleware.push(fn)
}
<!--http.createServer(callback)-->
callback() {
<!--第一步,利用koa-compose,整合全部的中間件-->
const fn = compose(this.middleware)
<!--閉包的形式返回-->
const handleRequest = (req, res) => {
<!--這裏面req是http中IncomingMessage ,res是ServerResponse 類-->
<!--createContext方法,下面解析-->
const ctx = this.createContext(req, res)
<!---->
return this.handleRequest(ctx, fn)
}
return handleRequest
}
}
複製代碼
<!--做爲調試源碼的程序-->
const Koa = require("Koa")
const app = new Koa()
app.use(async(ctx, next) => {
console.log(1)
await next()
console.log(2)
ctx.body = "這是第一個中間件"
})
app.use(async(ctx, next) => {
console.log(3)
await next()
console.log(4)
ctx.body = "這是第二個中間件"
})
app.listen(4001, () => { console.log("koa server is starting") })
複製代碼
koa源碼由application.js|context.js|request.js|response.js 文件組成,實際在application.js就引用了其餘三個js文件,並引用koa-compose核心文件,下面是本人經過vscode斷點調試跟蹤上面koa程序的流程圖,不過仍是但願想深刻學習的人,自行斷點調試,如圖mysql
注意:實心箭頭表明是請求後代碼的流程,線性箭頭是初始化服務加載的流程web
createContext(req, res) {
<!--this.context 引用個是require("./context"),下面的1.1章節-->
const context = Object.create(this.context)
<!--request、response都是先引用對應庫的基礎方法,而後從新建立個新的對象-->
const request = context.request = Object.create(this.request)
const response = context.response = Object.create(this.response)
<!--context對象包含request、response兩個對象-->
context.app = request.app = response.app = this
context.req = request.req = response.req = req
context.res = request.res = response.res = res
<!--request、response兩個對象又包含context-->
request.ctx = response.ctx = context
request.response = response
response.request = request
context.originalUrl = request.originalUrl = req.url
context.state = {}
return context
}
複製代碼
const delegate = require('delegates')
const proto = module.exports = {
<!--有個跟上述application.js文件中inspect相似的方法,省略-->
<!--set cookie 和 get cookie 方法-->
}
delegate(proto, 'response')
.method('set')
...
.access('body')
...
delegate(proto, 'request')
.method('get')
...
.access('querystring')
...
<!--根據npm官網對delegates的解釋就是委託事件,那麼context.js重點的做用是res,req兩個方法能加載這些委託的方法,換言之給context.js 返回的對象proto 對於request 、response 屬性增長增、讀取、設置、改變等基礎操做方法-->
複製代碼
利用上述的koa的啓動程序,查看createContext的返回結果,也就是context對象的方法屬性sql
{
"request": {
"method": "GET",
"url": "/favicon.ico",
"header": {
"host": "localhost:4001",
"connection": "keep-alive",
"pragma": "no-cache",
"cache-control": "no-cache",
"sec-fetch-mode": "no-cors",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36",
"accept": "image/webp,image/apng,image/*,*/*;q=0.8",
"sec-fetch-site": "same-origin",
"referer": "http://localhost:4001/",
"accept-encoding": "gzip, deflate, br",
"accept-language": "zh-CN,zh;q=0.9"
}
},
"response": {
"status": 200,
"message": "OK",
"header": {}
},
"app": {
"subdomainOffset": 2,
"proxy": false,
"env": "development"
},
"originalUrl": "/favicon.ico",
"req": "<original node req>",
"res": "<original node res>",
"socket": "<original node socket>"
}
複製代碼
www.jianshu.com/p/bca3a00f9… 這位博主說的挺詳細的,可是我打印出來的結果和上述「request、response是相互包含的關係,兩個對象又包含context」這句話相矛盾,若是你經過打斷點看,context和request、response是相互包含的關係npm
下面是koa-compose源碼主要部分api
function compose (middleware) {
<!--判斷是不是數組和函數,此處省略-->
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
<!--判斷 i<==index,若是 true 的話,則說明 next() 方法調用屢次,若是你在同一個中間件中執行屢次next()方法,會出現下面的錯誤-->
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, function next () {
return dispatch(i + 1)
})) }
-->
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
複製代碼
這裏面最重要的函數是:數組
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
複製代碼
一、上面代碼執行了中間件fn(context, next),並傳遞了 context 和 next 函數兩個參數,至於 next 函數則是返回一個 dispatch(i+1) 的執行結果;
二、這裏面用bind方式,保證在app.use()方法中使用await next()手動執行下一個中間件,因此舊方法用function next () { return dispatch(i + 1) })這種方式看起來更直觀;
三、最後(i+1)這個參數至關於執行了下一個中間件,從而造成遞歸調用。bash
閱讀koa中的源碼你會發現,它涉及最多的是閉包、高階函數、函數柯里化,好比說callback方法和handleRequest()方法restful
callback() {
const fn = compose(this.middleware)
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
};
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
複製代碼
一、callback方中handleRequest是一個閉包函數,由於它能訪問callback函數的做用域變量fn;
二、callback以handleRequest函數做爲返回值,而handleRequest又以this.handleRequest(ctx, fn)做爲返回值,而且this.handleRequest(ctx, fn)參數是fn也是函數,顯而易見這些函數都是高階函數cookie
又好比說下面一段代碼就是「函數柯里化」
<!--簡化版的koa-compose,middleware參數名變成fn-->
<!--compose函數柯里化-->
function compose (middleware) {
return function (context, next) {
return dispatch(0)
function dispatch (i) {
<!--middleware參數名變成fn-->
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
}
}
}
<!--callback方法中調用,可是fn沒有當即執行,是一個Promise對象-->
const fn = compose(this.middleware)
<!--handleRequest方法中fn名稱變成fnMiddleware,此方法執行後,也就執行了fn方法,也就是中間件方法,纔有中間件的回調,纔有啓動程序console.log的輸出-->
handleRequest(ctx, fnMiddleware) {
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
複製代碼
實現一個koa+mysql的簡單項目,可是很規範的restful API