<del id="d4fwx"><form id="d4fwx"></form></del>
      <del id="d4fwx"><form id="d4fwx"></form></del><del id="d4fwx"><form id="d4fwx"></form></del>

            <code id="d4fwx"><abbr id="d4fwx"></abbr></code>
          • 玩轉(zhuǎn)Koa之核心原理分析

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

            讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶,將通過(guò)不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:申請(qǐng)域名、網(wǎng)頁(yè)空間、營(yíng)銷(xiāo)軟件、網(wǎng)站建設(shè)、融安網(wǎng)站維護(hù)、網(wǎng)站推廣。

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

            • 封裝創(chuàng)建應(yīng)用程序函數(shù)
            • 擴(kuò)展res和req
            • 中間件實(shí)現(xiàn)原理
            • 異常處理

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

            利用NodeJS可以很容易編寫(xiě)一個(gè)簡(jiǎn)單的應(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)聽(tīng)請(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)聽(tīng)異常處理,則采用默認(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的引用,最后通過(guò)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)可以采用這樣的方式訪問(wèn)header屬性:

            ctx.request.header

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

            通過(guò)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 , 不過(guò)這個(gè)包時(shí)間久遠(yuǎn),有一些老方法已經(jīng)廢除。

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

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

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

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

            在connect中,開(kāi)發(fā)者可以通過(guò)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來(lái)檢測(cè)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);
            }

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

            下面是layer的處理流程圖:

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

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

            • 通過(guò)use方法注冊(cè)中間件;
            • 中間件的順序執(zhí)行是通過(guò)next方法銜接的并且需要手動(dòng)調(diào)用,在next中會(huì)進(jìn)行路由匹配,從而過(guò)濾掉部分中間件;
            • 當(dāng)中間件的執(zhí)行過(guò)程中發(fā)生異常,則next會(huì)攜帶異常過(guò)濾掉非錯(cuò)誤處理中間件,也是為什么錯(cuò)誤中間件會(huì)比其他中間件多一個(gè)error參數(shù);
            • 在請(qǐng)求處理的周期中,需要手動(dòng)調(diào)用res.end()來(lái)結(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中通過(guò)繼承event,暴露error事件讓開(kāi)發(fā)者自定義異常處理;
            • Koa中res.end由中間件執(zhí)行完成之后自動(dòng)調(diào)用,這樣避免在connect忘記調(diào)用res.end導(dǎo)致用戶得不到任何反饋。
            • Koa中采用了async/await語(yǔ)法讓開(kāi)發(fā)者利用同步的方式編寫(xiě)異步代碼。

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

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

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

            Koa中間件的執(zhí)行流程主要通過(guò)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)得更加簡(jiǎn)潔,主要原因在于:

            • connect中提供路由匹配的功能,而Koa中則是相當(dāng)于connect中默認(rèn)的'/'路徑。
            • connect在捕獲中間件的異常時(shí),通過(guò)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)該沒(méi)有什么疑問(wèn)了吧?!

            四、異常處理

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

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

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

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

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

            五、總結(jié)

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

            以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。

            網(wǎng)站欄目:玩轉(zhuǎn)Koa之核心原理分析
            網(wǎng)站鏈接:http://www.jbt999.com/article46/psehhg.html

            成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開(kāi)發(fā)、微信小程序、網(wǎng)站收錄手機(jī)網(wǎng)站建設(shè)、定制開(kāi)發(fā)、網(wǎng)站排名

            廣告

            聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:[email protected]。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

            商城網(wǎng)站建設(shè)

              <del id="d4fwx"><form id="d4fwx"></form></del>
              <del id="d4fwx"><form id="d4fwx"></form></del><del id="d4fwx"><form id="d4fwx"></form></del>

                    <code id="d4fwx"><abbr id="d4fwx"></abbr></code>
                  • 高清无码免费在线观看视频 | 狠狠干你| 黄色免费av| 亚洲男人网站 | 91av成人|