成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

玩轉(zhuǎn)Koa -- 核心原理分析

jsbintask / 3231人閱讀

摘要:三中間件實(shí)現(xiàn)原理首先需要明確是中間件并不是中的概念,它只是和框架衍生的概念。中間件的執(zhí)行流程主要由與函數(shù)決定依次取出中間件終止條件路由匹配規(guī)則函數(shù)中使用閉包函數(shù)來檢測是否與當(dāng)前路由相匹配,匹配則執(zhí)行該上的中間件函數(shù),否則繼續(xù)檢查下一個(gè)。

Koa作為下一代Web開發(fā)框架,不僅讓我們體驗(yàn)到了async/await語法帶來同步方式書寫異步代碼的酸爽,而且本身簡潔的特點(diǎn),更加利于開發(fā)者結(jié)合業(yè)務(wù)本身進(jìn)行擴(kuò)展。

??本文從以下幾個(gè)方面解讀Koa源碼:

封裝創(chuàng)建應(yīng)用程序函數(shù)

擴(kuò)展res和req

中間件實(shí)現(xiàn)原理

異常處理

一、封裝創(chuàng)建應(yīng)用程序函數(shù)

??利用NodeJS可以很容易編寫一個(gè)簡單的應(yīng)用程序:

const http = require("http")

const server = http.createServer((req, res) => {
  // 每一次請(qǐng)求處理的方法
  console.log(req.url)
  res.writeHead(200, { "Content-Type": "text/plain" })
  res.end("Hello NodeJS")
})

server.listen(8080)
注意:當(dāng)瀏覽器發(fā)送請(qǐng)求時(shí),會(huì)附帶請(qǐng)求/favicon.ico。

??而Koa在封裝創(chuàng)建應(yīng)用程序的方法中主要執(zhí)行了以下流程:

組織中間件(監(jiān)聽請(qǐng)求之前)

生成context上下文對(duì)象

執(zhí)行中間件

執(zhí)行默認(rèn)響應(yīng)方法或者異常處理方法

// application.js
listen(...args) {
  const server = http.createServer(this.callback());
  return server.listen(...args);
}

callback() {
  // 組織中間件
  const fn = compose(this.middleware);

  // 未監(jiān)聽異常處理,則采用默認(rèn)的異常處理方法
  if (!this.listenerCount("error")) this.on("error", this.onerror);

  const handleRequest = (req, res) => {
    // 生成context上下文對(duì)象
    const ctx = this.createContext(req, res);
    return this.handleRequest(ctx, fn);
  };

  return handleRequest;
}

handleRequest(ctx, fnMiddleware) {
  const res = ctx.res;
  // 默認(rèn)狀態(tài)碼為404
  res.statusCode = 404;
  // 中間件執(zhí)行完畢之后 采用默認(rèn)的 錯(cuò)誤 與 成功 的處理方式
  const onerror = err => ctx.onerror(err);
  const handleResponse = () => respond(ctx);
  onFinished(res, onerror);
  return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
二、擴(kuò)展res和req

??首先我們要知道NodeJS中的res和req是http.IncomingMessage和http.ServerResponse的實(shí)例,那么就可以在NodeJS中這樣擴(kuò)展req和res:

Object.defineProperties(http.IncomingMessage.prototype, {
  query: {
    get () {
      return querystring.parse(url.parse(this.url).query)
    }
  }
})

Object.defineProperties(http.ServerResponse.prototype, {
  json: {
    value: function (obj) {
      if (typeof obj === "object") {
        obj = JSON.stringify(obj)
      }
      this.end(obj)
    }
  }
})

??而Koa中則是自定義request和response對(duì)象,然后保持對(duì)res和req的引用,最后通過getter和setter方法實(shí)現(xiàn)擴(kuò)展。

// application.js
createContext(req, res) {
  const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req; // 保存原生req對(duì)象
    context.res = request.res = response.res = res; // 保存原生res對(duì)象
    request.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.state = {};
    // 最終返回完整的context上下文對(duì)象
    return context;
}

??所以在Koa中要區(qū)別這兩組對(duì)象:

request、response: Koa擴(kuò)展的對(duì)象

res、req: NodeJS原生對(duì)象

// request.js
get header() {
  return this.req.headers;
},
set header(val) {
  this.req.headers = val;
},

??此時(shí)已經(jīng)可以采用這樣的方式訪問header屬性:

  ctx.request.header

??但是為了方便開發(fā)者調(diào)用這些屬性和方法,Koa將response和request中的屬性和方法代理到context上。

??通過Object.defineProperty可以輕松的實(shí)現(xiàn)屬性的代理:

function access (proto, target, name) {
  Object.defineProperty(proto, name, {
    get () {
      return target[name]
    },
    set (value) {
      target[name] = value
    }
  })
}

access(context, request, "header")

??而對(duì)于方法的代理,則需要注意this的指向:

function method (proto, target, name) {
  proto[name] = function () {
    return target[name].apply(target, arguments)
  }
}

??上述就是屬性代理和方法代理的核心代碼,這基本算是一個(gè)常用的套路。

??代理這部分詳細(xì)的源碼,可以查看node-delegates, 不過這個(gè)包時(shí)間久遠(yuǎn),有一些老方法已經(jīng)廢除。

?? 在上述過程的源碼中涉及到很多JavaScript的基礎(chǔ)知識(shí),例如:原型繼承、this的指向。對(duì)于基礎(chǔ)薄弱的同學(xué),還需要先弄懂這些基礎(chǔ)知識(shí)。

三、中間件實(shí)現(xiàn)原理

?? 首先需要明確是:中間件并不是NodeJS中的概念,它只是connect、express和koa框架衍生的概念。

1、connect中間件的設(shè)計(jì)

??在connect中,開發(fā)者可以通過use方法注冊(cè)中間件:

 function use(route, fn) {
  var handle = fn;
  var path = route;

  // 不傳入route則默認(rèn)為"/",這種基本是框架處理參數(shù)的一種套路
  if (typeof route !== "string") {
    handle = route;
    path = "/";
  }

  ...
  // 存儲(chǔ)中間件
  this.stack.push({ route: path, handle: handle });
  
  // 以便鏈?zhǔn)秸{(diào)用
  return this;
}

??use方法內(nèi)部獲取到中間件的路由信息(默認(rèn)為"/")和中間件的處理函數(shù)之后,構(gòu)建成layer對(duì)象,然后將其存儲(chǔ)在一個(gè)隊(duì)列當(dāng)中,也就是上述代碼中的stack。

??connect中間件的執(zhí)行流程主要由handle與call函數(shù)決定:

function handle(req, res, out) {
  var index = 0;
  var stack = this.stack;
  ...
  function next(err) {
    ...
    // 依次取出中間件
    var layer = stack[index++]

    // 終止條件
    if (!layer) {
      defer(done, err);
      return;
    }

    var path = parseUrl(req).pathname || "/";
    var route = layer.route;

    // 路由匹配規(guī)則
    if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
      return next(err);
    }
    ...
    call(layer.handle, route, err, req, res, next);
  }

  next();
}

??handle函數(shù)中使用閉包函數(shù)next來檢測layer是否與當(dāng)前路由相匹配,匹配則執(zhí)行該layer上的中間件函數(shù),否則繼續(xù)檢查下一個(gè)layer。

??這里需要注意next中檢查路由的方式可能與想象中的不太一樣,所以默認(rèn)路由為"/"的中間件會(huì)在每一次請(qǐng)求處理中都執(zhí)行。

function call(handle, route, err, req, res, next) {
  var arity = handle.length;
  var error = err;
  var hasError = Boolean(err);

  try {
    if (hasError && arity === 4) {
      // 錯(cuò)誤處理中間件
      handle(err, req, res, next);
      return;
    } else if (!hasError && arity < 4) {
      // 請(qǐng)求處理中間件
      handle(req, res, next);
      return;
    }
  } catch (e) {
    // 記錄錯(cuò)誤
    error = e;
  }

  // 將錯(cuò)誤傳遞下去
  next(error);
}

??在通過call方法執(zhí)行中間件方法的時(shí)候,采用try/catch捕獲錯(cuò)誤,這里有一個(gè)特別需要注意的地方是,call內(nèi)部會(huì)根據(jù)是否存在錯(cuò)誤以及中間件函數(shù)的參數(shù)決定是否執(zhí)行錯(cuò)誤處理中間件。并且一旦捕獲到錯(cuò)誤,next方法會(huì)將錯(cuò)誤傳遞下去,所以接下來普通的請(qǐng)求處理中間件即使通過了next中的路由匹配,仍然會(huì)被call方法給過濾掉。

??下面是layer的處理流程圖:

??上述就是connect中間件設(shè)計(jì)的核心要點(diǎn),總結(jié)起來有如下幾點(diǎn):

通過use方法注冊(cè)中間件;

中間件的順序執(zhí)行是通過next方法銜接的并且需要手動(dòng)調(diào)用,在next中會(huì)進(jìn)行路由匹配,從而過濾掉部分中間件;

當(dāng)中間件的執(zhí)行過程中發(fā)生異常,則next會(huì)攜帶異常過濾掉非錯(cuò)誤處理中間件,也是為什么錯(cuò)誤中間件會(huì)比其他中間件多一個(gè)error參數(shù);

在請(qǐng)求處理的周期中,需要手動(dòng)調(diào)用res.end()來結(jié)束響應(yīng);

2、Koa中間件的設(shè)計(jì)

??Koa中間件與connect中間件的設(shè)計(jì)有很大的差異:

Koa中間件的執(zhí)行并不需要匹配路由,所以注冊(cè)的中間件每一次請(qǐng)求都會(huì)執(zhí)行。(當(dāng)然還是需要手動(dòng)調(diào)用next);

Koa中通過繼承event,暴露error事件讓開發(fā)者自定義異常處理;

Koa中res.end由中間件執(zhí)行完成之后自動(dòng)調(diào)用,這樣避免在connect忘記調(diào)用res.end導(dǎo)致用戶得不到任何反饋。

Koa中采用了async/await語法讓開發(fā)者利用同步的方式編寫異步代碼。

??當(dāng)然,Koa中也是采用use方法注冊(cè)中間件,相比較connect省去路由匹配的處理,就顯得很簡潔:

use(fn) {
  this.middleware.push(fn);
  return this;
}

??并且use支持鏈?zhǔn)秸{(diào)用。

??Koa中間件的執(zhí)行流程主要通過koa-compose中的compose函數(shù)完成:

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) {
    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 {
        // 遞歸調(diào)用下一個(gè)中間件
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); 
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

??看到這里本質(zhì)上connect與koa實(shí)現(xiàn)中間件的思想都是遞歸,不難看出koa相比較connect實(shí)現(xiàn)得更加簡潔,主要原因在于:

connect中提供路由匹配的功能,而Koa中則是相當(dāng)于connect中默認(rèn)的"/"路徑。

connect在捕獲中間件的異常時(shí),通過next攜帶error一個(gè)個(gè)中間件驗(yàn)證,直到錯(cuò)誤處理中間件,而Koa中則是用Promise包裝中間件,一旦中間件發(fā)生異常,那么會(huì)直接觸發(fā)reject狀態(tài),直接在Promise的catch中處理就行。

??上述就是connect中間件與Koa中間件的實(shí)現(xiàn)原理,現(xiàn)在在再看Koa中間件的這張執(zhí)行流程圖,應(yīng)該沒有什么疑問了吧?!

四、異常處理

??對(duì)于同步代碼,通過try/catch可以輕松的捕獲異常,在connect中間件的異常捕獲則是通過try/catch完成。

??對(duì)于異步代碼,try/catch則無法捕獲,這時(shí)候一般可以構(gòu)造Promise鏈,在最后的catch方法中捕獲錯(cuò)誤,Koa就是這樣處理,并且在catch方法中發(fā)送error事件,以便開發(fā)者自定義異常處理邏輯。

  this.app.emit("error", err, this);

??前面也談到Koa利用async/await語法帶來同步方式書寫異步代碼的酸爽,另外也讓錯(cuò)誤處理更加自然:

// 也可以這樣自定義錯(cuò)誤處理
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500
    ctx.body = err
  }
})
五、總結(jié)

??相信看到這里,再回憶一下之前遇到的那些問題,你應(yīng)該會(huì)有新的理解,并且再次使用Koa時(shí)會(huì)更加得心應(yīng)手,這也是分析Koa源碼的目的之一。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/100520.html

相關(guān)文章

  • 玩轉(zhuǎn)Koa -- koa-bodyparser原理解析

    摘要:主要通過處理二進(jìn)制數(shù)據(jù)流,但是它并不支持字符編碼方式,需要通過模塊進(jìn)行處理。最后留圖一張往期精彩回顧玩轉(zhuǎn)原理解析玩轉(zhuǎn)核心原理分析 一、前置知識(shí) ??在理解koa-bodyparser原理之前,首先需要了解部分HTTP相關(guān)的知識(shí)。 1、報(bào)文主體 ??HTTP報(bào)文主要分為請(qǐng)求報(bào)文和響應(yīng)報(bào)文,koa-bodyparser主要針對(duì)請(qǐng)求報(bào)文的處理。 ??請(qǐng)求報(bào)文主要由以下三個(gè)部分組成: 報(bào)文頭...

    andycall 評(píng)論0 收藏0
  • let-us-koa - 快速玩轉(zhuǎn) koa 的最小化 web 應(yīng)用 "容器"(腳

    摘要:搭建一個(gè)的腳手架并不困難,但就如其他體力活一樣,我們并不想重復(fù)勞動(dòng)其他腳手架生成出來的項(xiàng)目目錄結(jié)構(gòu)可能并不是我們想要的一些基于和類型的框架型項(xiàng)目集成了等重量級(jí)組件,當(dāng)然,這本身無可厚非,大家的定位不一樣我希望只通過一個(gè)最小化的容器去 Github Repo: https://github.com/qddegtya/let-us-koa showImg(https://segmentfa...

    史占廣 評(píng)論0 收藏0
  • 玩轉(zhuǎn)Koa -- koa-router原理解析

    摘要:四路由注冊(cè)構(gòu)造函數(shù)首先看了解一下構(gòu)造函數(shù)限制必須采用關(guān)鍵字服務(wù)器支持的請(qǐng)求方法,后續(xù)方法會(huì)用到保存前置處理函數(shù)存儲(chǔ)在構(gòu)造函數(shù)中初始化的和屬性最為重要,前者用來保存前置處理函數(shù),后者用來保存實(shí)例化的對(duì)象。 一、前言 ??Koa為了保持自身的簡潔,并沒有捆綁中間件。但是在實(shí)際的開發(fā)中,我們需要和形形色色的中間件打交道,本文將要分析的是經(jīng)常用到的路由中間件 -- koa-router。 ??...

    wthee 評(píng)論0 收藏0
  • 【全文】狼叔:如何正確的學(xué)習(xí)Node.js

    摘要:感謝大神的免費(fèi)的計(jì)算機(jī)編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因?yàn)橛腥嗽谥跎虾贜ode.js。那篇文章的反響還是相當(dāng)不錯(cuò)的,甚至連著名的hax賀老都很認(rèn)同,下班時(shí)讀那篇文章,竟然坐車的還坐過站了。大家可以很...

    Edison 評(píng)論0 收藏0
  • 【全文】狼叔:如何正確的學(xué)習(xí)Node.js

    摘要:感謝大神的免費(fèi)的計(jì)算機(jī)編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因?yàn)橛腥嗽谥跎虾贜ode.js。那篇文章的反響還是相當(dāng)不錯(cuò)的,甚至連著名的hax賀老都很認(rèn)同,下班時(shí)讀那篇文章,竟然坐車的還坐過站了。大家可以很...

    fengxiuping 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<