<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文搞懂 koa2 核心原理

          共 9704字,需瀏覽 20分鐘

           ·

          2021-06-22 08:43

          點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號

          回復(fù)算法,加入前端編程面試算法每日一題群

          koa的基礎(chǔ)結(jié)構(gòu)

          首先,讓我們認(rèn)識一下koa框架的定位——koa是一個(gè)精簡的node框架:

          • 它基于node原生req和res,封裝自定義的request和response對象,并基于它們封裝成一個(gè)統(tǒng)一的context對象。
          • 它基于async/await(generator)的洋蔥模型實(shí)現(xiàn)了中間件機(jī)制。

          koa框架的核心目錄如下:

          ── lib
             ├── application.js
             ├── context.js
             ├── request.js
             └── response.js

          // 每個(gè)文件的具體功能
          ── lib
             ├── new Koa()  || ctx.app
             ├── ctx
             ├── ctx.req  || ctx.request
             └── ctx.res  || ctx.response
          復(fù)制代碼
          undefined

          koa源碼基礎(chǔ)骨架

          application.js application.js是koa的主入口,也是核心部分,主要干了以下幾件事情:

          1. 完成了koa實(shí)例初始化的工作,啟動(dòng)服務(wù)器
          2. 實(shí)現(xiàn)了洋蔥模型的中間件機(jī)制
          3. 封裝了高內(nèi)聚的context對象
          4. 實(shí)現(xiàn)了異步函數(shù)的統(tǒng)一錯(cuò)誤處理機(jī)制

          context.js context.js主要干了兩件事情:

          1. 完成了錯(cuò)誤事件處理
          2. 代理了response對象和request對象的部分屬性和方法

          request.js request對象基于node原生req封裝了一系列便利屬性和方法,供處理請求時(shí)調(diào)用。所以當(dāng)你訪問ctx.request.xxx的時(shí)候,實(shí)際上是在訪問request對象上的setter和getter。

          response.js response對象基于node原生res封裝了一系列便利屬性和方法,供處理請求時(shí)調(diào)用。所以當(dāng)你訪問ctx.response.xxx的時(shí)候,實(shí)際上是在訪問response對象上的setter和getter。

          4個(gè)文件的代碼結(jié)構(gòu)如下:

          undefined
          undefined

          koa工作流

          Koa整個(gè)流程可以分成三步:

          1. 初始化階段

          new初始化一個(gè)實(shí)例,包括創(chuàng)建中間件數(shù)組、創(chuàng)建context/request/response對象,再使用use(fn)添加中間件到middleware數(shù)組,最后使用listen 合成中間件fnMiddleware,按照洋蔥模型依次執(zhí)行中間件,返回一個(gè)callback函數(shù)給http.createServer,開啟服務(wù)器,等待http請求。結(jié)構(gòu)圖如下圖所示:

          undefined
          1. 請求階段

          每次請求,createContext生成一個(gè)新的ctx,傳給fnMiddleware,觸發(fā)中間件的整個(gè)流程。3. 響應(yīng)階段 整個(gè)中間件完成后,調(diào)用respond方法,對請求做最后的處理,返回響應(yīng)給客戶端。

          koa中間件機(jī)制與實(shí)現(xiàn)

          koa中間件機(jī)制是采用koa-compose實(shí)現(xiàn)的,compose函數(shù)接收middleware數(shù)組作為參數(shù),middleware中每個(gè)對象都是async函數(shù),返回一個(gè)以context和next作為入?yún)⒌暮瘮?shù),我們跟源碼一樣,稱其為fnMiddleware在外部調(diào)用this.handleRequest的最后一行,運(yùn)行了中間件:fnMiddleware(ctx).then(handleResponse).catch(onerror);

          以下是koa-compose庫中的核心函數(shù):

          我們不禁會問:中間件中的next到底是什么呢?為什么執(zhí)行next就進(jìn)入到了下一個(gè)中間件了呢?中間件所構(gòu)成的執(zhí)行棧如下圖所示,其中next就是一個(gè)含有dispatch方法的函數(shù)。在第1個(gè)中間件執(zhí)行next時(shí),相當(dāng)于在執(zhí)行dispatch(2),就進(jìn)入到了下一個(gè)中間件的處理流程。因?yàn)?code style="">dispatch返回的都是Promise對象,因此在第n個(gè)中間件await next()時(shí),就進(jìn)入到了第n+1個(gè)中間件,而當(dāng)?shù)趎+1個(gè)中間件執(zhí)行完成后,可以返回第n個(gè)中間件。但是在某個(gè)中間件中,我們沒有寫next(),就不會再執(zhí)行它后面所有的中間件。運(yùn)行機(jī)制如下圖所示:

          undefined

          koa-convert解析

          在koa2中引入了koa-convert庫,在使用use函數(shù)時(shí),會使用到convert方法(只展示核心的代碼):

          const convert = require('koa-convert');

          module.exports = class Application extends Emitter {
              use(fn) {
                  if (typeof fn !== 'function'throw new TypeError('middleware must be a function!');
                  if (isGeneratorFunction(fn)) {
                      deprecate('Support for generators will be removed';
                      fn = convert(fn);
                  }
                  debug('use %s', fn._name || fn.name || '-');
                  this.middleware.push(fn);
                  return this;
              }
          }
          復(fù)制代碼

          koa2框架針對koa1版本作了兼容處理,中間件函數(shù)如果是generator函數(shù)的話,會使用koa-convert進(jìn)行轉(zhuǎn)換為“類async函數(shù)”。首先我們必須理解generatorasync的區(qū)別:async函數(shù)會自動(dòng)執(zhí)行,而generator每次都要調(diào)用next函數(shù)才能執(zhí)行,因此我們需要尋找到一個(gè)合適的方法,讓next()函數(shù)能夠一直持續(xù)下去即可,這時(shí)可以將generatoryieldvalue指定成為一個(gè)Promise對象。下面看看koa-convert中的核心代碼:

          const co = require('co')
          const compose = require('koa-compose')

          module.exports = convert

          function convert (mw{
            if (typeof mw !== 'function') {
              throw new TypeError('middleware must be a function')
            }
            if (mw.constructor.name !== 'GeneratorFunction') {
              return mw
            }
            const converted = function (ctx, next{
              return co.call(ctx, mw.call(ctx, createGenerator(next)))
            }
            converted._name = mw._name || mw.name
            return converted
          }
          復(fù)制代碼

          首先針對傳入的參數(shù)mw作校驗(yàn),如果不是函數(shù)則拋異常,如果不是generator函數(shù)則直接返回,如果是generator函數(shù)則使用co函數(shù)進(jìn)行處理。co的核心代碼如下:

          function co(gen{
            var ctx = this;
            var args = slice.call(arguments1);
            
            return new Promise(function(resolve, reject{
              if (typeof gen === 'function') gen = gen.apply(ctx, args);
              if (!gen || typeof gen.next !== 'function'return resolve(gen);

              onFulfilled();
              
              function onFulfilled(res{
                var ret;
                try {
                  ret = gen.next(res);
                } catch (e) {
                  return reject(e);
                }
                next(ret);
                return null;
              }

              function onRejected(err{
                var ret;
                try {
                  ret = gen.throw(err);
                } catch (e) {
                  return reject(e);
                }
                next(ret);
              }

              function next(ret{
                if (ret.done) return resolve(ret.value);
                var value = toPromise.call(ctx, ret.value);
                if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
                return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
                  + 'but the following object was passed: "' + String(ret.value) + '"'));
              }
            });
          }
          復(fù)制代碼

          由以上代碼可以看出,co中作了這樣的處理:

          1. 把一個(gè)generator封裝在一個(gè)Promise對象中
          2. 這個(gè)Promise對象再次把它的gen.next()也封裝出Promise對象,相當(dāng)于這個(gè)子Promise對象完成的時(shí)候也重復(fù)調(diào)用gen.next()
          3. 當(dāng)所有迭代完成時(shí),對父Promise對象進(jìn)行resolve

          以上工作完成后,就形成了一個(gè)類async函數(shù)。

          異步函數(shù)的統(tǒng)一錯(cuò)誤處理機(jī)制

          在koa框架中,有兩種錯(cuò)誤的處理機(jī)制,分別為:

          1. 中間件捕獲
          2. 框架捕獲
          undefined

          中間件捕獲是針對中間件做了錯(cuò)誤處理響應(yīng),如fnMiddleware(ctx).then(handleResponse).catch(onerror),在中間件運(yùn)行出錯(cuò)時(shí),會出發(fā)onerror監(jiān)聽函數(shù)。框架捕獲是在context.js中作了相應(yīng)的處理this.app.emit('error', err, this),這里的this.app是對application的引用,當(dāng)context.js調(diào)用onerror時(shí),實(shí)際上是觸發(fā)application實(shí)例的error事件 ,因?yàn)?code style="">Application類是繼承自EventEmitter類的,因此具備了處理異步事件的能力,可以使用EventEmitter類中對于異步函數(shù)的錯(cuò)誤處理方法。

          koa為什么能實(shí)現(xiàn)異步函數(shù)的統(tǒng)一錯(cuò)誤處理?因?yàn)閍sync函數(shù)返回的是一個(gè)Promise對象,如果async函數(shù)內(nèi)部拋出了異常,則會導(dǎo)致Promise對象變?yōu)閞eject狀態(tài),異常會被catch的回調(diào)函數(shù)(onerror)捕獲到。如果await后面的Promise對象變?yōu)閞eject狀態(tài),reject的參數(shù)也可以被catch的回調(diào)函數(shù)(onerror)捕獲到。

          委托模式在koa中的應(yīng)用

          delegates庫由知名的 TJ 所寫,可以幫我們方便快捷地使用設(shè)計(jì)模式當(dāng)中的委托模式,即外層暴露的對象將請求委托給內(nèi)部的其他對象進(jìn)行處理。

          delegates 基本用法就是將內(nèi)部對象的變量或者函數(shù)綁定在暴露在外層的變量上,直接通過 delegates 方法進(jìn)行如下委托,基本的委托方式包含:

          • getter:外部對象可以直接訪問內(nèi)部對象的值
          • setter:外部對象可以直接修改內(nèi)部對象的值
          • access:包含 getter 與 setter 的功能
          • method:外部對象可以直接調(diào)用內(nèi)部對象的函數(shù)

          delegates 原理就是__defineGetter__和__defineSetter__。在application.createContext函數(shù)中,被創(chuàng)建的context對象會掛載基于request.js實(shí)現(xiàn)的request對象和基于response.js實(shí)現(xiàn)的response對象。下面2個(gè)delegate的作用是讓context對象代理request和response的部分屬性和方法:

          undefined

          做了以上的處理之后,context.request的許多屬性都被委托在context上了,context.response的許多方法都被委托在context上了,因此我們不僅可以使用this.ctx.request.xxthis.ctx.response.xx取到對應(yīng)的屬性,還可以通過this.ctx.xx取到this.ctx.requestthis.ctx.response下掛載的xx方法。

          我們在源碼中可以看到,response.js和request.js使用的是get set代理,而context.js使用的是delegate代理,為什么呢?因?yàn)閐elegate方法比較單一,只代理屬性;但是使用set和get方法還可以加入一些額外的邏輯處理。在context.js中,只需要代理屬性即可,使用delegate方法完全可以實(shí)現(xiàn)此效果,而在response.js和request.js中是需要處理其他邏輯的,如以下對query作的格式化操作:

          get query() {
            const str = this.querystring;
            const c = this._querycache = this._querycache || {};
            return c[str] || (c[str] = qs.parse(str));
          }
          復(fù)制代碼

          到這里,相信你對koa2的原理實(shí)現(xiàn)有了更深的理解吧?

          關(guān)于本文

          作者:會吃魚的貓咪

          https://juejin.cn/post/6966432934756794405

          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認(rèn)真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
           》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持


          瀏覽 65
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91精品久久久久久久久久久久 | 国产影视AV | 日本黄色电影视频 | 黄色视频网站观看 | 天天想夜夜操 |