Koa源碼閱讀筆記(1) -- co

本筆記共四篇
Koa源碼閱讀筆記(1) -- co
Koa源碼閱讀筆記(2) -- compose
Koa源碼閱讀筆記(3) -- 服務器の啓動與請求處理
Koa源碼閱讀筆記(4) -- ctx對象javascript

原由

在7月23號時,我參加了北京的NodeParty。其中第一場演講就是深刻講解Koa。
因爲演講只有一個小時,講不完Koa的原理。因而在聽的時候以爲並非很知足,遂開始本身翻看源代碼。
而Koa1是基於ES6的generator的。其在Koa1中的運行依賴於co。
正好本身以前也想看co的源代碼,因此趁着這個機會,一口氣將其讀完。前端

co

關於co,其做者的介紹非常簡單。java

The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)node

而co的意義,則在於使用generator函數,解決了JavaScript的回調地獄問題git

源碼解讀

co的源代碼十分簡潔,一共才兩百餘行。並且裏面註釋到位,因此閱讀起來的難度仍是不大的。
co的核心代碼以下(已加上本身的註釋):es6

/**
 * Execute the generator function or a generator
 * and return a promise.
 *
 * @param {Function} fn
 * @return {Promise}
 * @api public
 */

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) {
    // 啓動generator函數。
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    // 若是gen不存在或者gen.next不是函數(非generator函數)則返回空值
    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 return的對象
        // gen.next(res),則是向generator函數傳參數,做爲yield的返回值
        /**
         * yield句自己沒有返回值,或者說老是返回undefined。
         * next方法能夠帶一個參數,該參數就會被看成上一個yield語句的返回值。
         * [next方法的參數](http://es6.ruanyifeng.com/#docs/generator#next方法的參數)
         */
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      // 在這兒,每完成一次yield,便交給next()處理
      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函數完成了,返回最終的值
      // 在全部yield完成後,調用next()會返回{value: undefined, done: true}
      // 因此須要手動return一個值。這樣最後的value纔不是undefined
      if (ret.done) return resolve(ret.value);
      // 未完成則統一交給toPromise函數去處理
      // 這裏的ret.value實際是 yield 後面的那個(對象|函數|值) 好比 yield 'hello', 此時的value則是 'hello'
      var value = toPromise.call(ctx, ret.value);
      // 這裏value.then(onFulfilled, onRejected),實際上已經調用並傳入了 onFulfilled, onRejected 兩個參數。
      // 由於非這些對象,沒法調用then方法。也就沒法使用onFulfilled
      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) + '"'));
    }
  });
}

/**
 * Convert a `yield`ed value into a promise.
 *
 * @param {Mixed} obj
 * @return {Promise}
 * @api private
 */

function toPromise(obj) {
  if (!obj) return obj;
  if (isPromise(obj)) return obj;
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  if ('function' == typeof obj) return thunkToPromise.call(this, obj);
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  if (isObject(obj)) return objectToPromise.call(this, obj);
  return obj;
}

co的運行機制

看完了源代碼,對generator函數有更深的理解,也理解了co的運行機制。github

自動執行generator

首先解決的問題則是自動執行generator函數是如何實現的。
這兒的核心部分則在於:segmentfault

function co(gen) {
  if (typeof gen === 'function') gen = gen.apply(ctx, args);
  onFulfilled();

  function onFulfilled(res) {
    var ret;
    try {
      ret = gen.next(res);
    } 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) + '"'));
  }
}

這兒,在給co傳入一個generator函數後,co會將其自動啓動。而後調用onFulfilled函數。
onFulfilled函數內部,首先則是獲取next的返回值。交由next函數處理。
next函數則首先判斷是否完成,若是這個generator函數完成了,返回最終的值。
不然則將yield後的值,轉換爲Promise
最後,經過Promise的then,並將onFulfilled函數做爲參數傳入。api

if (value && isPromise(value)) {
  return value.then(onFulfilled, onRejected);
}

而在generator中,yield句自己沒有返回值,或者說老是返回undefined
而next方法能夠帶一個參數,該參數就會被看成上一個yield語句的返回值。
同時經過onFulfilled函數,則能夠實現自動調用。
這也就能解釋爲何co基於Promise。且能自動執行了。promise

結語

co的源代碼讀取來不難,但其處理方式卻使人讚歎。
並且generator函數的使用,對ES7中的Async/Await的產生,起了關鍵做用。
正如其做者TJ在co的說明文檔中所說的那樣:

Co is a stepping stone towards ES7 async/await.

雖說我沒用過co,只使用過Async/Await
但現在的Async/Await,使用babel,啓用transform-async-to-generator插件,轉譯後,也是編譯爲generator函數。
因此瞭解一下,仍是有好處的。並且閱讀co的源代碼,是閱讀koa1源碼的必經之路。


前端路漫漫,且行且歌。

最後附上本人博客地址和原文連接,但願能與各位多多交流。

Lxxyx的前端樂園
原文連接:Koa源碼閱讀筆記(1) -- co

相關文章
相關標籤/搜索