學習 koa 源碼的總體架構,淺析koa洋蔥模型原理和co原理

前言

這是 學習源碼總體架構系列第七篇。總體架構這詞語好像有點大,姑且就算是源碼總體結構吧,主要就是學習是代碼總體結構,不深究其餘不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。

學習源碼總體架構系列文章以下:html

1. 學習 jQuery 源碼總體架構 | 2. 學習 underscore 源碼總體架構 | 3. 學習 lodash 源碼總體架構 | 4. 學習 sentry 源碼總體架構
| 5. 學習 vuex 源碼總體架構 | 6. 學習 axios 源碼總體架構

感興趣的讀者能夠點擊閱讀。

其餘源碼計劃中的有:expressvue-rotuerreduxreact-redux 等源碼,不知什麼時候能寫完(哭泣),歡迎持續關注我(若川)。前端

源碼類文章,通常閱讀量不高。已經有能力看懂的,本身就看了。不想看,不敢看的就不會去看源碼。

因此個人文章,儘可能寫得讓想看源碼又不知道怎麼看的讀者能看懂。vue

若是你簡歷上一不當心寫了熟悉koa,面試官大機率會問:node

一、 koa洋蔥模型怎麼實現的。

二、若是中間件中的 next()方法報錯了怎麼辦。

三、 co的原理是怎樣的。

等等問題

導讀

文章經過例子調試koa,梳理koa的主流程,來理解koa-compose洋蔥模型原理和co庫的原理,相信看完必定會有所收穫。react

本文目錄

本文學習的koa版本是v2.11.0。克隆的官方倉庫的master分支。
截至目前(2020年3月11日),最新一次commit2020-01-04 07:41 Olle Jonsson eda27608build: Drop unused Travis sudo: false directive (#1416)ios

本文倉庫在這裏若川的 koa-analysis github 倉庫 https://github.com/lxchuan12/koa-analysis。求個star呀。git

本文閱讀最佳方式

star一下個人倉庫,再把它git clone https://github.com/lxchuan12/koa-analysis.git克隆下來。不用管你是否用過nodejs。會一點點promise、generator、async、await等知識便可看懂。若是一點點也不會,能夠邊看阮一峯老師的《ES6標準入門》相關章節。跟着文章節奏調試和示例代碼調試,動手調試(用vscode或者chrome)印象更加深入。文章長段代碼不用細看,能夠調試時再細看。看這類源碼文章百遍,可能不如本身多調試幾遍。也歡迎加我微信交流lxchuan12es6

# 克隆個人這個倉庫
git clone https://github.com/lxchuan12/koa-analysis.git
# chrome 調試:
# 全局安裝 http-server
npm i -g http-server
hs koa/examples/
# 能夠指定端口 -p 3001
# hs -p 3001 koa/examples/
# 瀏覽器中打開
# 而後在瀏覽器中打開localhost:8080,開心的把代碼調試起來

這裏把這個examples文件夾作個簡單介紹。
github

  • middleware文件夾是用來vscode調試總體流程的。
  • simpleKoa 文件夾是koa簡化版,爲了調試koa-compose洋蔥模型如何串聯起來各個中間件的。
  • koa-convert文件夾是用來調試koa-convertco源碼的。
  • co-generator文件夾是模擬實現co的示例代碼。

vscode 調試 koa 源碼方法

以前,我在知乎回答了一個問題一年內的前端看不懂前端框架源碼怎麼辦?
推薦了一些資料,閱讀量還不錯,你們有興趣能夠看看。主要有四點:
web

1.藉助調試

2.搜索查閱相關高贊文章

3.把不懂的地方記錄下來,查閱相關文檔

4.總結

看源碼,調試很重要,因此我詳細寫下 koa 源碼調試方法,幫助一些可能不知道如何調試的讀者。

# 我已經克隆到個人koa-analysis倉庫了
git clone https://github.com/koajs/koa.git
// package.json
{
  "name": "koa",
  "version": "2.11.0",
  "description": "Koa web app framework",
  "main": "lib/application.js",
}

克隆源碼後,看package.json找到main,就知道入口文件是lib/application.js了。

大概看完項目結構後發現沒有examples文件夾(通常項目都會有這個文件夾,告知用戶如何使用該項目),這時仔細看README.md
若是看英文README.md有些吃力,會發如今Community標題下有一個中文文檔 v2.x。同時也有一個examples倉庫

# 我已經克隆下來到個人倉庫了
git clone https://github.com/koajs/examples.git

這時再開心的把examples克隆到本身電腦。能夠安裝好依賴,逐個研究學習下這裏的例子,而後可能就一不當心掌握了koa的基本用法。固然,我這裏不詳細寫這一塊了,我是本身手寫一些例子來調試。

繼續看文檔會發現使用指南講述編寫中間件

使用文檔中的中間件koa-compose例子來調試

學習 koa-compose 前,先看兩張圖。

洋蔥模型示意圖

洋蔥模型中間件示意圖

koa中,請求響應都放在中間件的第一個參數context對象中了。

再引用Koa中文文檔中的一段:

若是您是前端開發人員,您能夠將 next(); 以前的任意代碼視爲「捕獲」階段,這個簡易的 gif 說明了 async 函數如何使咱們可以恰當地利用堆棧流來實現請求和響應流:

中間件gif圖

  1. 建立一個跟蹤響應時間的日期
  2. 等待下一個中間件的控制
  3. 建立另外一個日期跟蹤持續時間
  4. 等待下一個中間件的控制
  5. 將響應主體設置爲「Hello World」
  6. 計算持續時間
  7. 輸出日誌行
  8. 計算響應時間
  9. 設置 X-Response-Time 頭字段
  10. 交給 Koa 處理響應

讀者們看完這個gif圖,也能夠思考下如何實現的。根據表現,能夠猜想是next是一個函數,並且返回的多是一個promise,被await調用。

看到這個gif圖,我把以前寫的examples/koa-compose的調試方法含淚刪除了。默默寫上gif圖上的這些代碼,想着這個讀者們更容易讀懂。
我把這段代碼寫在這裏 koa/examples/middleware/app.js便於調試。

在項目路徑下配置新建.vscode/launch.json文件,program配置爲本身寫的koa/examples/middleware/app.js文件。

<details>
<summary>.vscode/launch.json 代碼,點擊這裏展開/收縮,能夠複製</summary>

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "啓動程序",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/koa/examples/middleware/app.js"
        }
    ]
}

</details>

F5鍵開始調試,調試時先走主流程,必要的地方打上斷點,不用一開始就關心細枝末節。

斷點調試要領:

賦值語句能夠一步跳過,看返回值便可,後續詳細再看。

函數執行須要斷點跟着看,也能夠結合註釋和上下文倒推這個函數作了什麼。

上述比較囉嗦的寫了一堆調試方法。主要是想着授人予魚不如授人予漁,這樣換成其餘源碼也會調試了。

簡單說下chrome調試nodejschrome瀏覽器打開chrome://inspect,點擊配置configure...配置127.0.0.1:端口號(端口號在Vscode 調試控制檯顯示了)。

更多能夠查看English Debugging Guide

中文調試指南

喜歡看視頻的讀者也能夠看慕課網這個視頻node.js調試入門,講得仍是比較詳細的。

不過我感受在chrome調試nodejs項目體驗不是很好(多是我方式不對),因此我大部分具體的代碼時都放在html文件script形式,在chrome調試了。

先看看 new Koa() 結果app是什麼

看源碼我習慣性看它的實例對象結構,通常全部屬性和方法都放在實例對象上了,並且會經過原型鏈查找形式查找最頂端的屬性和方法。

koa/examples/middleware/app.js文件調試時,先看下執行new Koa()以後,app是什麼,有個初步印象。

// 文件 koa/examples/middleware/app.js
const Koa = require('../../lib/application');

// const Koa = require('koa');
// 這裏打個斷點
const app = new Koa();
// x-response-time

// 這裏打個斷點
app.use(async (ctx, next) => {

});

在調試控制檯ctrl + 反引號鍵(通常在Tab上方的按鍵)喚起,輸入app,按enter鍵打印app。會有一張這樣的圖。

koa 實例對象調試圖

VScode也有一個代碼調試神器插件Debug Visualizer

安裝好後插件後,按ctrl + shift + p,輸入Open a new Debug Visualizer View,來使用,輸入app,顯示是這樣的。

koa 實例對象可視化簡版

不過目前體驗來看,相對還比較雞肋,只能顯示一級,並且只能顯示對象,相信之後會更好。更多玩法能夠查看它的文檔。

我把koa實例對象比較完整的用xmind畫出來了,大概看看就好,有個初步印象。

koa 實例對象

接着,咱們能夠看下app 實例、context、request、request的官方文檔。

app 實例、context、request、request 官方API文檔

能夠真正使用的時候再去仔細看文檔。

koa 主流程梳理簡化

經過F5啓動調試(直接跳到下一個斷點處)F10單步跳過F11單步調試等,配合重要的地方斷點,調試完總體代碼,其實比較容易整理出以下主流程的代碼。

class Emitter{
  // node 內置模塊
  constructor(){
  }
}
class Koa extends Emitter{
  constructor(options){
    super();
    options = options || {};
    this.middleware = [];
    this.context = {
      method: 'GET',
      url: '/url',
      body: undefined,
      set: function(key, val){
        console.log('context.set', key, val);
      },
    };
  }
  use(fn){
    this.middleware.push(fn);
    return this;
  }
  listen(){
    const  fnMiddleware = compose(this.middleware);
    const ctx = this.context;
    const handleResponse = () => respond(ctx);
    const onerror = function(){
      console.log('onerror');
    };
    fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
}
function respond(ctx){
  console.log('handleResponse');
  console.log('response.end', ctx.body);
}

重點就在listen函數裏的compose這個函數,接下來咱們就詳細來欣賞下這個函數。

koa-compose 源碼(洋蔥模型實現)

經過app.use() 添加了若干函數,可是要把它們串起來執行呀。像上文的gif圖同樣。

compose函數,傳入一個數組,返回一個函數。對入參是否是數組和校驗數組每一項是否是函數。

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!')
  }

 //  傳入對象 context 返回Promise
  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)
      }
    }
  }
}

把簡化的代碼和koa-compose代碼寫在了一個文件中。koa/examples/simpleKoa/koa-compose.js

hs koa/examples/
# 而後能夠打開localhost:8080/simpleKoa,開心的把代碼調試起來

不過這樣好像仍是有點麻煩,我還把這些代碼放在codepen https://codepen.io/lxchuan12/pen/wvarPEb中,直接能夠在線調試啦。是否是以爲很貼心^_^,本身多調試幾遍便於消化理解。

你會發現compose就是相似這樣的結構(移除一些判斷)。

// 這樣就可能更好理解了。
// simpleKoaCompose
const [fn1, fn2, fn3] = this.middleware;
const fnMiddleware = function(context){
    return Promise.resolve(
      fn1(context, function next(){
        return Promise.resolve(
          fn2(context, function next(){
              return Promise.resolve(
                  fn3(context, function next(){
                    return Promise.resolve();
                  })
              )
          })
        )
    })
  );
};
fnMiddleware(ctx).then(handleResponse).catch(onerror);
也就是說 koa-compose返回的是一個 PromisePromise中取出第一個函數( app.use添加的中間件),傳入 context和第一個 next函數來執行。

第一個 next函數裏也是返回的是一個 PromisePromise中取出第二個函數( app.use添加的中間件),傳入 context和第二個 next函數來執行。

第二個 next函數裏也是返回的是一個 PromisePromise中取出第三個函數( app.use添加的中間件),傳入 context和第三個 next函數來執行。

第三個...

以此類推。最後一箇中間件中有調用 next函數,則返回 Promise.resolve。若是沒有,則不執行 next函數。
這樣就把全部中間件串聯起來了。這也就是咱們常說的洋蔥模型。

不得不說很是驚豔,「玩仍是大神會玩」

這種把函數存儲下來的方式,在不少源碼中都有看到。好比lodash源碼的惰性求值,vuex也是把action等函數存儲下,最後纔去調用。

搞懂了koa-compose 洋蔥模型實現的代碼,其餘代碼就不在話下了。

錯誤處理

中文文檔 錯誤處理

仔細看文檔,文檔中寫了三種捕獲錯誤的方式。

// application.js 文件
class Application extends Emitter {
  // 代碼有簡化組合
  listen(){
    const  fnMiddleware = compose(this.middleware);
    if (!this.listenerCount('error')) this.on('error', this.onerror);
    const onerror = err => ctx.onerror(err);
    fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
  onerror(err) {
    // 代碼省略
    // ...
  }
}

ctx.onerror

lib/context.js文件中,有一個函數onerror,並且有這麼一行代碼this.app.emit('error', err, this)

module.exports = {
  onerror(){
    // delegate
    // app 是在new Koa() 實例
    this.app.emit('error', err, this);
  }
}
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    err.status = err.statusCode || err.status || 500;
    throw err;
  }
});

try catch 錯誤或被fnMiddleware(ctx).then(handleResponse).catch(onerror);,這裏的onerrorctx.onerror

ctx.onerror函數中又調用了this.app.emit('error', err, this),因此在最外圍app.on('error',err => {})能夠捕獲中間件鏈中的錯誤。
由於koa繼承自events模塊,因此有'emit'和on等方法)

koa2 和 koa1 的簡單對比

中文文檔中描述了 koa2 和 koa1 的區別

koa1中主要是generator函數。koa2中會自動轉換generator函數。

// Koa 將轉換
app.use(function *(next) {
  const start = Date.now();
  yield next;
  const ms = Date.now() - start;
  console.log(`${this.method} ${this.url} - ${ms}ms`);
});

koa-convert 源碼

vscode/launch.json文件,找到這個program字段,修改成"program": "${workspaceFolder}/koa/examples/koa-convert/app.js"

經過F5啓動調試(直接跳到下一個斷點處)F10單步跳過F11單步調試調試走一遍流程。重要地方斷點調試。

app.use時有一層判斷,是不是generator函數,若是是則用koa-convert暴露的方法convert來轉換從新賦值,再存入middleware,後續再使用。

class Koa 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 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;
  }
}

koa-convert源碼挺多,核心代碼實際上是這樣的。

function convert(){
 return function (ctx, next) {
    return co.call(ctx, mw.call(ctx, createGenerator(next)))
  }
  function * createGenerator (next) {
    return yield next()
  }
}

最後仍是經過co來轉換的。因此接下來看co的源碼。

co 源碼

tj大神寫的co 倉庫

本小節的示例代碼都在這個文件夾koa/examples/co-generator中,hs koa/example,能夠自行打開https://localhost:8080/co-generator調試查看。

co源碼前,先看幾段簡單代碼。

// 寫一個請求簡版請求
function request(ms= 1000) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({name: '若川'});
    }, ms);
  });
}
// 獲取generator的值
function* generatorFunc(){
  const res = yield request();
  console.log(res, 'generatorFunc-res');
}
generatorFunc(); // 報告,我不會輸出你想要的結果的

簡單來講co,就是把generator自動執行,再返回一個promise
generator函數這玩意它不自動執行呀,還要一步步調用next(),也就是叫它走一步才走一步

因此有了async、await函數。

// await 函數 自動執行
async function asyncFunc(){
    const res = await request();
    console.log(res, 'asyncFunc-res await 函數 自動執行');
}
asyncFunc(); // 輸出結果

也就是說co須要作的事情,是讓generatorasync、await函數同樣自動執行。

模擬實現簡版 co(初版)

這時,咱們來模擬實現初版的co。根據generator的特性,其實容易寫出以下代碼。

// 獲取generator的值
function* generatorFunc(){
  const res = yield request();
  console.log(res, 'generatorFunc-res');
}

function coSimple(gen){
  gen = gen();
  console.log(gen, 'gen');

  const ret = gen.next();
  const promise = ret.value;
  promise.then(res => {
    gen.next(res);
  });
}
coSimple(generatorFunc);
// 輸出了想要的結果
// {name: "若川"}"generatorFunc-res"

模擬實現簡版 co(第二版)

可是實際上,不會上面那麼簡單的。有多是多個yield和傳參數的狀況。
傳參能夠經過這以下兩行代碼來解決。

const args = Array.prototype.slice.call(arguments, 1);
gen = gen.apply(ctx, args);

兩個yield,我大不了從新調用一下promise.then,搞定。

// 多個yeild,傳參狀況
function* generatorFunc(suffix = ''){
  const res = yield request();
  console.log(res, 'generatorFunc-res' + suffix);

  const res2 = yield request();
  console.log(res2, 'generatorFunc-res-2' + suffix);
}

function coSimple(gen){
  const ctx = this;
  const args = Array.prototype.slice.call(arguments, 1);
  gen = gen.apply(ctx, args);
  console.log(gen, 'gen');

  const ret = gen.next();
  const promise = ret.value;
  promise.then(res => {
    const ret = gen.next(res);
    const promise = ret.value;
      promise.then(res => {
        gen.next(res);
      });
  });
}

coSimple(generatorFunc, ' 哎呀,我真的是後綴');

模擬實現簡版 co(第三版)

問題是確定不止兩次,無限次的yield的呢,這時確定要把重複的封裝起來。並且返回是promise,這就實現了以下版本的代碼。

function* generatorFunc(suffix = ''){
  const res = yield request();
  console.log(res, 'generatorFunc-res' + suffix);

  const res2 = yield request();
  console.log(res2, 'generatorFunc-res-2' + suffix);

  const res3 = yield request();
  console.log(res3, 'generatorFunc-res-3' + suffix);

  const res4 = yield request();
  console.log(res4, 'generatorFunc-res-4' + suffix);
}

function coSimple(gen){
  const ctx = this;
  const args = Array.prototype.slice.call(arguments, 1);
  gen = gen.apply(ctx, args);
  console.log(gen, 'gen');

  return new Promise((resolve, reject) => {

    onFulfilled();

    function onFulfilled(res){
      const ret = gen.next(res);
      next(ret);
    }

    function next(ret) {
      const promise = ret.value;
      promise && promise.then(onFulfilled);
    }

  });
}

coSimple(generatorFunc, ' 哎呀,我真的是後綴');

但第三版的模擬實現簡版co中,尚未考慮報錯和一些參數合法的狀況。

最終來看下co源碼

這時來看看co的源碼,報錯和錯誤的狀況,錯誤時調用reject,是否是就好理解了一些呢。

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1)

  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  return new Promise(function(resolve, reject) {
    // 把參數傳遞給gen函數並執行
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    // 若是不是函數 直接返回
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFulfilled();

    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */

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

    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */

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

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */

    // 反覆執行調用本身
    function next(ret) {
      // 檢查當前是否爲 Generator 函數的最後一步,若是是就返回
      if (ret.done) return resolve(ret.value);
      // 確保返回值是promise對象。
      var value = toPromise.call(ctx, ret.value);
      // 使用 then 方法,爲返回值加上回調函數,而後經過 onFulfilled 函數再次調用 next 函數。
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      // 在參數不符合要求的狀況下(參數非 Thunk 函數和 Promise 對象),將 Promise 對象的狀態改成 rejected,從而終止執行。
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}

koa 和 express 簡單對比

中文文檔 koa 和 express 對比

文檔裏寫的挺全面的。簡單來講koa2語法更先進,更容易深度定製(egg.jsthink.js、底層框架都是koa)。

總結

文章經過授人予魚不如授人予魚的方式,告知如何調試源碼,看完了koa-compose洋蔥模型實現,koa-convertco等源碼。

koa-compose是將app.use添加到middleware數組中的中間件(函數),經過使用Promise串聯起來,next()返回的是一個promise

koa-convert 判斷app.use傳入的函數是不是generator函數,若是是則用koa-convert來轉換,最終仍是調用的co來轉換。

co源碼實現原理:其實就是經過不斷的調用generator函數的next()函數,來達到自動執行generator函數的效果(相似async、await函數的自動自行)。

koa框架總結:主要就是四個核心概念,洋蔥模型(把中間件串聯起來),http請求上下文(context)、http請求對象、http響應對象。

本文倉庫在這裏若川的 koa-analysis github 倉庫 https://github.com/lxchuan12/koa-analysis。求個star呀。

git clone https://github.com/lxchuan12/koa-analysis.git

再強烈建議下按照本文閱讀最佳方式,克隆代碼下來,動手調試代碼學習更加深入

若是讀者發現有不妥或可改善之處,再或者哪裏沒寫明白的地方,歡迎評論指出,也歡迎加我微信交流 lxchuan12。另外以爲寫得不錯,對您有些許幫助,能夠點贊、評論、轉發分享,也是對筆者的一種支持,萬分感謝。

解答下開頭的提問

僅供參考

一、 koa洋蔥模型怎麼實現的。

能夠參考上文整理的簡版koa-compose做答。

// 這樣就可能更好理解了。
// simpleKoaCompose
const [fn1, fn2, fn3] = this.middleware;
const fnMiddleware = function(context){
    return Promise.resolve(
      fn1(context, function next(){
        return Promise.resolve(
          fn2(context, function next(){
              return Promise.resolve(
                  fn3(context, function next(){
                    return Promise.resolve();
                  })
              )
          })
        )
    })
  );
};
fnMiddleware(ctx).then(handleResponse).catch(onerror);

答:app.use() 把中間件函數存儲在middleware數組中,最終會調用koa-compose導出的函數compose返回一個promise,中間函數的第一個參數ctx是包含響應和請求的一個對象,會不斷傳遞給下一個中間件。next是一個函數,返回的是一個promise

二、若是中間件中的 next()方法報錯了怎麼辦。

可參考上文整理的錯誤處理做答。

ctx.onerror = function {
  this.app.emit('error', err, this);
};
  listen(){
    const  fnMiddleware = compose(this.middleware);
    if (!this.listenerCount('error')) this.on('error', this.onerror);
    const onerror = err => ctx.onerror(err);
    fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
  onerror(err) {
    // 代碼省略
    // ...
  }

答:中間件鏈錯誤會由ctx.onerror捕獲,該函數中會調用this.app.emit('error', err, this)(由於koa繼承自events模塊,因此有'emit'和on等方法),可使用app.on('error', (err) => {}),或者app.onerror = (err) => {}進行捕獲。

三、 co的原理是怎樣的。

答: co的原理是經過不斷調用 generator函數的 next方法來達到自動執行 generator函數的,相似 async、await函數自動執行。

答完,面試官可能以爲小夥子仍是蠻懂koa的啊。固然也可能繼續追問,直到答不出...

還能作些什麼 ?

學完了總體流程,koa-composekoa-convertco的源碼。

還能仔細看看看http請求上下文(context)、http請求對象、http響應對象的具體實現。

還能根據我文章說的調試方式調試koa 組織中的各類中間件,好比koa-bodyparser, koa-routerkoa-jwtkoa-sessionkoa-cors等等。

還能把examples倉庫克隆下來,個人這個倉庫已經克隆了,挨個調試學習下源碼。

web框架有不少,好比Express.jsKoa.jsEgg.jsNest.jsNext.jsFastify.jsHapi.jsRestify.jsLoopback.ioSails.jsMidway.js等等。

還能把這些框架的優點劣勢、設計思想等學習下。

還能繼續學習HTTP協議、TCP/IP協議網絡相關,雖然不屬於koa的知識,但需深刻學習掌握。

學無止境~~~

推薦閱讀

koa 官網 | koa 倉庫 | koa 組織 | koa2 中文文檔 | co 倉庫

知乎@姚大帥:多是目前市面上比較有誠意的Koa2源碼解讀

知乎@零小白:十分鐘帶你看完 KOA 源碼

微信開放社區@小丹の:多是目前最全的koa源碼解析指南

IVWEB官方帳號: KOA2框架原理解析和實現

深刻淺出vue.js 做者 berwin: 深刻淺出 Koa2 原理

阮一峯老師:co 函數庫的含義和用法

另外一個系列

面試官問:JS的繼承

面試官問:JS的this指向

面試官問:可否模擬實現JS的call和apply方法

面試官問:可否模擬實現JS的bind方法

面試官問:可否模擬實現JS的new操做符

關於

做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。

若川的博客,使用vuepress重構了,閱讀體驗可能更好些

掘金專欄,歡迎關注~

segmentfault前端視野專欄,歡迎關注~

知乎前端視野專欄,歡迎關注~

語雀前端視野專欄,新增語雀專欄,歡迎關注~

github blog,相關源碼和資源都放在這裏,求個star^_^~

歡迎加微信交流 微信公衆號

可能比較有趣的微信公衆號,長按掃碼關注。歡迎加我微信lxchuan12(註明來源,基原本者不拒),拉您進【前端視野交流羣】,長期交流學習~

若川視野

相關文章
相關標籤/搜索