標籤:data index 多個 理解 操作 ast geo 三次 pch
koa是有express原班人馬打造的基於node.js的下一代web開發架構。koa 1.0使用generator實現非同步,相比於回調簡單和優雅和不少。koa團隊並沒有止步於koa 1.0, 隨著node.js開始支援async/await,他們又馬不停蹄的發布了koa 2.0,koa2完全使用Promise並配合async/await來實現非同步,使得非同步作業更臻完美。
一、快速開始
koa使用起來非常簡單,安裝好node.js後執行以下命令安裝koa:
npm init
npm install --save koa
一個簡單的Hello World程式開場,
//index.js
const Koa = require(‘koa‘)
const app = new Koa()
app.use( async ctx => {
ctx.body = ‘Hello World‘
})
app.listen(3000,()=>{
console.log("server is running at 3000 port");
})
在命令列執行
node index.js
開啟瀏覽器查看http://localhost:3000就可以看到頁面輸出的 Hello World。
中介軟體 middleware
Koa中使用 app.use()用來載入中介軟體,基本上Koa 所有的功能都是通過中介軟體實現的。
中介軟體的設計非常巧妙,多個中介軟體會形成一個棧結構(middle stack),以”先進後出”(first-in-last-out)的順序執行。每個中介軟體預設接受兩個參數,第一個參數是 Context 對象,第二個參數是 next函數。只要調用 next函數,就可以把執行權轉交給下一個中介軟體,最裡層的中介軟體執行完後有會把執行權返回給上一級調用的中介軟體。整個執行過程就像一個剝洋蔥的過程。
比如你可以通過在所有中介軟體的頂端添加以下中介軟體來列印請求日誌到控制台:
app.use(async function (ctx, next) {
let start = new Date()
await next()
let ms = new Date() - start
console.log(‘%s %s - %s‘, ctx.method, ctx.url, ms)
})
常用的中介軟體列表可以在這裡找到: https://github.com/koajs/koa/wiki
二、koa源碼解讀
開啟項目根目錄下的node_modules檔案夾,開啟並找到koa的檔案夾,如下所示:
開啟lib檔案夾,這裡一共有4個檔案,
application.js - koa主程式入口
context.js - koa中介軟體參數ctx對象的封裝
request.js - request對象封裝
response.js - response對象封裝
我們這裡主要看下application.js,我這裡摘取了主要功能相關的 代碼如下:
/**
* Shorthand for:
*
* http.createServer(app.callback()).listen(...)
*
* @param {Mixed} ...
* @return {Server}
* @api public
*/
listen(...args) {
debug(‘listen‘);
const server = http.createServer(this.callback());
return server.listen(...args);
}
/**
* Use the given middleware `fn`.
*
* Old-style middleware will be converted.
*
* @param {Function} fn
* @return {Application} self
* @api public
*/
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;
}
/**
* Return a request handler callback
* for node‘s native http server.
*
* @return {Function}
* @api public
*/
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;
}
/**
* Handle request in callback.
*
* @api private
*/
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服務物件並啟動。我們注意到 callback()方法裡面有這樣一段代碼 :
const fn = compose(this.middleware);
compose其實是Node模組koa-compose,它的作用是將多個中介軟體函數合并成一個大的中介軟體函數,然後調用這個中介軟體函數就可以依次執行添加的中介軟體函數,執行一系列的任務。遇到await next()時就停止當前中介軟體函數的執行並把執行權交個下一個中介軟體函數,最後next()執行完返回上一個中介軟體函數繼續執行下面的代碼。
它是用了什麼黑魔法實現的呢?我們開啟node_modules/koa-compose/index.js,代碼如下 :
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)
}
}
}
}
乍一看好難好複雜,沒事,我們一步一步的來梳理一下。
這個方法裡面的核心就是dispatch函數(廢話,整個compose方法就返回了一個函數)。沒有辦法簡寫,但是我們可以將dispatch函數類似遞迴的調用展開,以三個中介軟體為例:
第一次,此時第一個中介軟體被調用,dispatch(0),展開:
Promise.resolve(function(context, next){
//中介軟體一第一部分代碼
await/yield next();
//中介軟體一第二部分代碼}());
很明顯這裡的next指向dispatch(1),那麼就進入了第二個中介軟體;
第二次,此時第二個中介軟體被調用,dispatch(1),展開:
Promise.resolve(function(context, 中介軟體2){
//中介軟體一第一部分代碼
await/yield Promise.resolve(function(context, next){
//中介軟體二第一部分代碼
await/yield next();
//中介軟體二第二部分代碼
}())
//中介軟體一第二部分代碼}());
很明顯這裡的next指向dispatch(2),那麼就進入了第三個中介軟體;
第三次,此時第二個中介軟體被調用,dispatch(2),展開:
Promise.resolve(function(context, 中介軟體2){
//中介軟體一第一部分代碼
await/yield Promise.resolve(function(context, 中介軟體3){
//中介軟體二第一部分代碼
await/yield Promise(function(context){
//中介軟體三代碼
}());
//中介軟體二第二部分代碼
})
//中介軟體一第二部分代碼}());
此時中介軟體三代碼執行完畢,開始執行中介軟體二第二部分代碼,執行完畢,開始執行中間一第二部分代碼,執行完畢,所有中介軟體載入完畢。
再舉一個例子加深下理解。建立index.js並粘貼如下代碼:
const compose = require(‘koa-compose‘)
const middleware1 = (ctx, next) => {
console.log(‘here is in middleware1, before next:‘);
next();
console.log(‘middleware1 end‘);
}
const middleware2 = (ctx, next) => {
console.log(‘here is in middleware2, before next:‘);
next();
console.log(‘middleware2 end‘);
}
const middleware3 = (ctx, next) => {
console.log(‘here is in middleware3, before next:‘);
next();
console.log(‘middleware3 end‘);
}
const middlewares = compose([middleware1, middleware2, middleware3])
console.dir(middlewares())
在命令列輸入node index.js執行,輸出結果如下:
here is in middleware1, before next:
here is in middleware2, before next:
here is in middleware3, before next:
middleware3 end
middleware2 end
middleware1 end
Promise { undefined }
可以看到每個中介軟體都按照“剝洋蔥”的流程一次執行。當我們初始化app對象並調用app.use()時,就是在不斷往app.middleware數組裡添加中介軟體函數,當調用app.listen()再執行組合出來的函數。
-END-
轉載請註明來源
掃描下方二維碼,或者搜尋 前端提高班 關注公眾號,即可擷取最新走心文章
記得把我設為星標或置頂哦
在公眾號後台回複 前端資源 即可擷取最新前端開發資源
koa源碼解讀