koa2第二篇: 圖解中介軟體原始碼執行過程
阿新 • • 發佈:2019-01-06
中介軟體
首先寫一個簡單的中介軟體demo:
const Koa = require('koa')
const app = new Koa()
const port = 3000
const ctx1 = async (ctx, next) => {
console.log('開始執行中介軟體1')
await next()
ctx.response.type = 'text/html'
ctx.response.body = '<h3>hello world</h3>'
console.log('結束執行中介軟體1' )
}
app.use(ctx1)
app.use(async function ctx2 (ctx, next) {
console.log('開始執行中介軟體2')
await next()
console.log('結束執行中介軟體2')
})
app.listen(port, () => {
console.log(`server is running on the port: ${port}`)
})
複製程式碼
很明顯中介軟體執行順序是這樣的:
開始執行中介軟體1
開始執行中介軟體2
結束執行中介軟體2
結束執行中介軟體1
複製程式碼
你可以理解為koa2會先按照中介軟體註冊順序執行next()之前的程式碼, 執行完到底部之後, 返回往前執行next()之後的程式碼。
重點是我們需要koa2原始碼究竟是怎麼樣執行的? 現在開始除錯模式進入koa2原始碼一探究竟。
- 首先在兩個中介軟體註冊的地方打了斷點
- 我們可以看到koa2是先按照你中介軟體的順序去註冊執行
- 然後會進入callback. 這是因為
// 應用程式
app.listen(port, () => {
console.log(`server is running on the port: ${port}`)
})
// 原始碼
listen(...args) {
debug('listen' );
const server = http.createServer(this.callback());
return server.listen(...args);
}
複製程式碼
這個時候this.middleware已經存了兩個中介軟體。
- 這個時候你請求一個路由比如
http://localhost:3000/a
複製程式碼
koa2的中介軟體處理就是在這個函式裡面
callback() {
// compose()這是處理中介軟體的執行順序所在
}
複製程式碼
於是我們進入這個koa-compose的原始碼看下:
'use strict'
/**
* Expose compositor.
*/
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
// 首先是一些中介軟體格式校驗
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
let index = -1
// 返回一個函式, 從第一個中介軟體開始執行, 可以通過next()呼叫後續中介軟體
return dispatch(0)
// dispatch始終返回一個Promise物件
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 {
// next即就是通過dispatch(i+1)來執行下一個中介軟體
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
// 捕獲中介軟體中發生的異常
return Promise.reject(err)
}
}
}
}
複製程式碼
此時i=0取出第一個中介軟體,由於閉包原因i是一直存在的。
這個時候可以看到fn就是ctx1。
注意
// next即就是通過dispatch(i+1)來執行下一個中介軟體
dispatch.bind(null, i + 1)
複製程式碼
這個時候開始進入第一個中介軟體執行第一句console.log('開始執行中介軟體1')
這裡也能看到next指的就是前面提到的dispatch.bind。
然後我們繼續單步除錯進入這句
// ctx1中的
await next()
複製程式碼
此時又重新進入compose(), 繼續執行下一個中介軟體, i=1
取出第二個中介軟體函式ctx2。
此時進入第二個中介軟體ctx2開始執行console.log('開始執行中介軟體2')
繼續單步除錯
此時i=2,fx=undefined
// 這個洋蔥模型的最後做一個兜底的處理
if (!fn) return Promise.resolve()
複製程式碼
執行中介軟體ctx2的第二句console