欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2018年12月29日 14:41:07   作者:descire  
這篇文章主要介紹了玩轉(zhuǎn)Koa之核心原理分析,本文從封裝創(chuàng)建應(yīng)用程序函數(shù)、擴(kuò)展res和req、中間件實(shí)現(xiàn)原理、異常處理的等這幾個(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) => {
 // 每一次請求處理的方法
 console.log(req.url)
 res.writeHead(200, { 'Content-Type': 'text/plain' })
 res.end('Hello NodeJS')
})

server.listen(8080)

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

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

  • 組織中間件(監(jiān)聽請求之前)
  • 生成context上下文對象
  • 執(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上下文對象
  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對象,然后保持對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對象
  context.res = request.res = response.res = res; // 保存原生res對象
  request.ctx = response.ctx = context;
  request.response = response;
  response.request = request;
  context.originalUrl = request.originalUrl = req.url;
  context.state = {};
  // 最終返回完整的context上下文對象
  return context;
}

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

  • request、response: Koa擴(kuò)展的對象
  • res、req: NodeJS原生對象
// 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')

而對于方法的代理,則需要注意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ǔ)知識,例如:原型繼承、this的指向。對于基礎(chǔ)薄弱的同學(xué),還需要先弄懂這些基礎(chǔ)知識。

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

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

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

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

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

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

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

use方法內(nèi)部獲取到中間件的路由信息(默認(rèn)為'/')和中間件的處理函數(shù)之后,構(gòu)建成layer對象,然后將其存儲在一個(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ì)在每一次請求處理中都執(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) {
   // 請求處理中間件
   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ò)誤傳遞下去,所以接下來普通的請求處理中間件即使通過了next中的路由匹配,仍然會(huì)被call方法給過濾掉。

下面是layer的處理流程圖:

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

  • 通過use方法注冊中間件;
  • 中間件的順序執(zhí)行是通過next方法銜接的并且需要手動(dòng)調(diào)用,在next中會(huì)進(jìn)行路由匹配,從而過濾掉部分中間件;
  • 當(dāng)中間件的執(zhí)行過程中發(fā)生異常,則next會(huì)攜帶異常過濾掉非錯(cuò)誤處理中間件,也是為什么錯(cuò)誤中間件會(huì)比其他中間件多一個(gè)error參數(shù);
  • 在請求處理的周期中,需要手動(dòng)調(diào)用res.end()來結(jié)束響應(yīng);

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

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

  • Koa中間件的執(zhí)行并不需要匹配路由,所以注冊的中間件每一次請求都會(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方法注冊中間件,相比較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)該沒有什么疑問了吧?!

四、異常處理

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

對于異步代碼,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源碼的目的之一。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • node環(huán)境下運(yùn)行js代碼缺少window環(huán)境的原因以及解決方法

    node環(huán)境下運(yùn)行js代碼缺少window環(huán)境的原因以及解決方法

    Node是一個(gè)基于Chrome?V8引擎的運(yùn)行環(huán)境,讓JavaScript運(yùn)行在服務(wù)端的開發(fā)平臺,這篇文章主要給大家介紹了關(guān)于node環(huán)境下運(yùn)行js代碼缺少window環(huán)境的原因以及解決方法,需要的朋友可以參考下
    2023-11-11
  • Windows7系統(tǒng)下如何安裝nodejs16以上版本

    Windows7系統(tǒng)下如何安裝nodejs16以上版本

    這篇文章主要給大家介紹了關(guān)于Windows7系統(tǒng)下如何安裝nodejs16以上版本的相關(guān)資料,很多時(shí)候node.js的版本存在兼容,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07
  • 傻瓜式解讀koa中間件處理模塊koa-compose的使用

    傻瓜式解讀koa中間件處理模塊koa-compose的使用

    這篇文章主要介紹了傻瓜式解讀koa中間件處理模塊koa-compose的使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-10-10
  • 深入解讀Node.js中的koa源碼

    深入解讀Node.js中的koa源碼

    這篇文章主要介紹了深入解讀Node.js中的koa源碼,任何一個(gè)框架的出現(xiàn)都是為了解決問題,而Koa則是為了更方便的構(gòu)建http服務(wù)而出現(xiàn)的。 可以簡單的理解為一個(gè)HTTP服務(wù)的中間件框架。,需要的朋友可以參考下
    2019-06-06
  • 為什么Node.js會(huì)這么火呢?Node.js流行的原因

    為什么Node.js會(huì)這么火呢?Node.js流行的原因

    是什么原因讓Node.js突然間如此流行呢?聽起來像是有了一種新的Web開發(fā)技術(shù),是這樣嗎?我們來匯總一下。
    2014-12-12
  • Nodejs進(jìn)階:核心模塊net入門學(xué)習(xí)與實(shí)例講解

    Nodejs進(jìn)階:核心模塊net入門學(xué)習(xí)與實(shí)例講解

    本篇文章主要是介紹了Nodejs之NET模塊,net模塊是同樣是nodejs的核心模塊,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2016-11-11
  • 教你徹底搞懂ESM與CJS互相轉(zhuǎn)換

    教你徹底搞懂ESM與CJS互相轉(zhuǎn)換

    這篇文章主要為大家介紹了ESM與CJS互相轉(zhuǎn)換的理解與實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • 解決node-sass下載不成功的問題

    解決node-sass下載不成功的問題

    這篇文章主要介紹了解決node-sass下載不成功的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • nodejs的http和https下載遠(yuǎn)程資源post數(shù)據(jù)實(shí)例

    nodejs的http和https下載遠(yuǎn)程資源post數(shù)據(jù)實(shí)例

    這篇文章主要為大家介紹了nodejs的http和https下載遠(yuǎn)程資源post數(shù)據(jù)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • node.js中的path.dirname方法使用說明

    node.js中的path.dirname方法使用說明

    這篇文章主要介紹了node.js中的path.dirname方法使用說明,本文介紹了path.dirname的方法說明、語法、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12

最新評論