koa源碼解讀

来源:https://www.cnblogs.com/lightzone/archive/2018/10/05/9746334.html
-Advertisement-
Play Games

koa是有express原班人馬打造的基於node.js的下一代web開發框架。koa 1.0使用generator實現非同步,相比於回調簡單和優雅和不少。koa團隊並沒有止步於koa 1.0, 隨著node.js開始支持async/await,他們又馬不停蹄的發佈了koa 2.0,koa2完全使用P... ...


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-

 

轉載請註明來源

掃描下方二維碼,或者搜索 前端提高班 關註公眾號,即可獲取最新走心文章

記得把我設為星標或置頂哦

在公眾號後臺回覆 前端資源 即可獲取最新前端開發資源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 閑來無事就來搞虛擬機裝操作系統!期間出現很多錯誤,分享一下 一。安裝虛擬機 二。準備安裝的鏡像文件 我下載的是windows7純凈版 深度技術裡面下載的(http://www.xitongzhijia.net/win7/201805/125616.html),在系統之家的安裝不成功 三。準備安裝 1 ...
  • 如下所示,在創建LV的時候,偶爾會遇到“Volume group "xxxx" has insufficient free space (xxxx extents): xxxx required”這類錯誤。表示Volume group的可用空間不夠了。如下測試所示: root@mylnx12:~# ... ...
  • Oracle建表參考網址:http://www.oraclejsq.com/getOracle_jcjc.do?nodeid=010100139 ...
  • MySQL邏輯架構圖 第一層:主要功能是連接處理、授權認證、安全等。相當於JavaEE中的常說的Web層 第二層:包含了MySQL服務端的核心功能,包含查詢緩存、查詢解析、分析、優化等功能。相當於JavaEE中的Service層 第三層:主要是存儲引擎,存儲引擎主要負責數據的存儲和提取。相當於Jav ...
  • 雖然同表級(DML)觸發器和庫級(DDL)觸發器共頂著一個帽子,但登陸觸發器與二者有本質區別。無論表級還是庫級,都是用來進行數據管理的,而登陸觸發器是純粹的安全工具。 登陸觸發器只響應LOGON事件,在登陸資料庫成功後、用戶會話未實際建立前觸發。登陸資料庫失敗,如賬號密碼錯誤,不會激發登陸觸發器。登 ...
  • 觸發器分為兩種,一種與數據表綁定,響應數據表指定動作(insert、delete或update),此處稱為表級;一種與資料庫本身綁定,響應數據定義(DDL)語句(主要是CREATE、ALTER 和 DROP 開頭的語句),此處稱為庫級。本篇以下所說觸發器皆指庫級觸發器。 觸發器(又名DDL觸發器)是 ...
  • Hadoop概要 到底是業務推動了技術的發展,還是技術推動了業務的發展,這個話題放在什麼時候都會惹來一些爭議。 隨著互聯網以及物聯網的蓬勃發展,我們進入了大數據時代。IDC預測,到2020年,全球會有44ZB的數據量。 傳統存儲和技術架構無法滿足需求 。在2013年出版的《大數據時代》一書中,定義了 ...
  • ORA-01153: an incompatible media recovery is active的處理 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...