從react-start到co源碼(三)

react做爲當前十分流行的前端框架,相信不少前端er都有蠢蠢欲動的學習它的想法。工欲善其事,必先利其器。這篇文章就簡單的給你們介紹一下如何我快速的搭建一個react前端開發環境。主要針對於react小白,大神不喜勿噴。
從標題能夠看出,這裏不會僅僅只介紹一下react的開發環境如何搭建。我將這個系列分紅三篇介紹:前端


該篇是這個系列文章的第三篇,主要是對co的源碼進行分析講解。co的源碼十分簡單,但實現的功能倒是十分的強大。不瞭解的同窗能夠經過co自行學習,也能夠經過我這篇源碼分析的文章進行更深刻的學習。segmentfault

co源碼歸納

co源碼主要包含了兩部分:公共方法和私有方法。數組

一、公共方法promise

  • co前端框架

  • co.wrapapp

二、私有方法框架

  • isObject

  • isGeneratorFunction

  • isGenerator

  • isPromise

  • objectToPromise

  • arrayToPromise

  • thunkToPromise

  • toPromise

源碼的閱讀順序建議先閱讀私有方法的部分,而後在閱讀公共方法的部分。各個部分的閱讀順序也按照上面列舉的順序進行閱讀。

co源碼分析

/**
 * slice() reference.
 */

var slice = Array.prototype.slice;

/**
 * Expose `co`.
 */

module.exports = co['default'] = co.co = co;

co.wrap = function (fn) {
  // 這個方法的主要做用就是將generator函數轉化成普通的函數調用  有點相似於thunk函數的轉化
  /**
   * function* a(val) {
   *   return val
   * }
   *
   * console.log(co(a,'pavoooo'))
   * console.log(co.wrap(a)('pavoooo'))  就能夠這樣調用了
   *  */
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
};

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

  // co的調用結果是一個promise對象
  return new Promise(function(resolve, reject) {
    // 若是co的第一個參數是函數的話 就將第二個以及後續的參數傳遞給這個函數
    // 並將gen的調用結果賦給gen
    /**
     * co(function(){
     *    console.log(arguments)
     *  }, 1, 2, 3)
     * 不考慮下面轉化的狀況 這個函數運行以後 會打印出{ '0': 1, '1': 2, '2': 3 }
     * 同時gen的值就是undefined
     */
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    // 這個條件判斷的就是 若是gen調用以後的返回值是undefined或者不是一個generator函數 直接將promise的狀態轉化成resolved
    // 同時將返回值做爲resolved的狀態值釋放 也就是說co函數的參數應該是一個generator函數
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    // 調用onFulfilled函數--遞歸的調用generator函數的next方法
    onFulfilled();

    function onFulfilled(res) {
      var ret;
      try {
        // 這個語句有兩重做用
        // 1、接收上一個yield返回的值
        // 2、將調用以後的遍歷器賦值給ret並傳遞到next函數中以判斷gen調用是否結束
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      // 遞歸的調用next 也是是遞歸的執行gen函數的yield語句
      next(ret);
    }

    function onRejected(err) {
      var ret;
      try {
        // 這個函數主要是當yield後的語句不符合規定的類型的時候 向外拋出一個錯誤
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    function next(ret) {
      // 若是generator函數運行結束 直接釋放結果 這個結果就是gen函數中return的結果 這就能夠在外部經過then方法接收
      if (ret.done) return resolve(ret.value);
      // 不然將遍歷器對應的value轉化成promise
      var value = toPromise.call(ctx, ret.value);
      // 若是可以成功的轉化成promise 調用then方法 將值釋放出來 並將其做爲onFulfilled函數的參數  而在onFulfilled函數內部  又經過
      // gen.next()接收 這樣 就能夠把每次gen.next().value保存在gen函數內部的變量
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      // 這裏表示傳遞給co函數的generator函數的yield後的語句必須是一個function, promise, generator, array, or object
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}

function toPromise(obj) {
  // 這個函數實際上是對下面幾種將元素轉化成promise對象的幾個函數的集合  這樣作就不須要在各個函數中分別判斷值的類型而後
  // 調用不一樣的方法  統一交給這個函數根據不一樣的值的類型調用不一樣的轉換函數
  // obj是假值
  if (!obj) return obj;
  // obj是promise
  if (isPromise(obj)) return obj;
  // obj是generation
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  // obj是thunk函數
  if ('function' == typeof obj) return thunkToPromise.call(this, obj);
  // obj是數組
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  // obj是對象
  if (isObject(obj)) return objectToPromise.call(this, obj);
  // obj是普通類型的數據且爲真 如字符串  數字等
  return obj;
}

function thunkToPromise(fn) {
  // thunk函數轉換成promise
  // js的thunk函數就是將多參數函數轉換成單參數函數的一種方式
  // 約定俗成的也是第一個參數是error對象 後續的參數是函數的返回值
  var ctx = this;
  return new Promise(function (resolve, reject) {
    fn.call(ctx, function (err, res) {
      // error不爲空 直接將promise轉化成rejected狀態
      if (err) return reject(err);
      // 不然將函數轉化成resolve狀態
      if (arguments.length > 2) res = slice.call(arguments, 1);
      resolve(res);
    });
  });
}

function arrayToPromise(obj) {
  // 數組轉化成promise--
  // 先將數組中的各個元素轉化成promise 而後經過Promise.all進行包裝  轉化成一個新的promise實例並返回
  return Promise.all(obj.map(toPromise, this));
}

/**
 * 這個函數是將一個對象轉換成promise對象 從isPromise函數的內部可知
 * 把對象轉換成promise對象的前提就是 這個對象必須具備then方法 也是是必須是一個thenable對象
 */

function objectToPromise(obj){
  // 經過obj的constructor 建立出一個新的對象 這個對象擁有obj全部繼承的屬性 這樣就能夠在這個對象上進行轉化 從而防止了更改源對象
  var results = new obj.constructor();
  //獲取到obj的全部非繼承屬性的鍵組成的數組
  var keys = Object.keys(obj);
  // 定義一個promise容器  並傳遞給Promise.all這個方法
  var promises = [];
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    // 根據值的類型調用對應的轉換函數轉換成promise對象
    var promise = toPromise.call(this, obj[key]);
    // 這個if條件中的第一個條件 是容錯處理 若是isPromise用的不少的狀況下  建議將這個容錯處理
    // 放在isPromise函數中 轉換以後的值是promise 就調用then方法  取出promise對象中返回的值  
    // 而後其設置爲對應鍵的值
    /**
     * 也就是說 若是一個對象是以下的形式:
     * var a = {
     *    p: new Promise((resolve, reject) => {
     *       resolve(2)
     * }) 
     * }
     * 
     * 通過defer函數的轉換以後a.p = 3
     */
    if (promise && isPromise(promise)) defer(promise, key);
    // 若是不是promise就直接返回對應的值
    else results[key] = obj[key];
  }
  // 經過Promise.all將多個promise實例轉化成一個新的實例 並返回
  return Promise.all(promises).then(function () {
    return results;
  });

  function defer(promise, key) {
    // predefine the key in the result
    results[key] = undefined;
    promises.push(promise.then(function (res) {
      results[key] = res;
    }));
  }
}

/**
 * 判斷obj是否是一個promise對象 
 * 根據promise A+的規範
 * 一個合格的promise必須是一個thenable對象也就是其必須提供一個then方法來獲取值
 * 因此咱們能夠經過判斷一個對象是否具備then方法來判斷是否是promise對象 但這不是絕對準確的方法 co內部經過Promise.all這個
 * 方法對isPromise()返回true的對象進行了封裝  均可以將其轉化成promise對象  因此在使用的時候不須要過多的擔憂
 */

function isPromise(obj) {
  return 'function' == typeof obj.then;
}

function isGenerator(obj) {
  return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}

/** 
 * 判斷參數obj是否是一個generator函數
 */
function isGeneratorFunction(obj) {
  var constructor = obj.constructor;
  if (!constructor) return false;
  /**
   * 判斷是否是一個generator函數
   * function* gen() {}  ====> gen.constructor.name = 'GeneratorFunction'
   * 同時這個if條件也是對如下的這種狀況做的判斷
   * function* gen() {}  
   * var g = gen() ====> g.constructor.name = 'GeneratorFunction'
   * 
   * displayName是一個非標準的屬性 用於返回函數顯示的名稱 不推薦使用
   *  */
  if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
   /**
   * obj是經過原生的generator函數操做得出  即
   * obj = generator()
   * obj = Object.create(generator)
   * obj = Object.create(generator())
   * 
   * 上面if條件都會返回true
   * 
   * 下面的這個isGenerator函數  筆者猜想
   * 一是對原生generator函數調用以後返回的迭代器的判斷
   * 而是對自定義的generator函數的判斷
   * 好比這種形式的返回結果也是true
   * function A() {}
   * A.prototype.next = function() {
   *    return {
   *        value: 1,
   *        done: false
   *     }
   * }
   * Aa.prototype.throw = function() {}
   *
   * var a = new A()
   * console.log(isGeneratorFunction(a))
   *  */
  return isGenerator(constructor.prototype);
}

/**
 * 用於判斷一個對象是否是純粹的js對象
 * js中純粹的對象通常有三種建立方式
 * var obj = {}
 * var obj = new Object
 * var obj = Object.create(Object.prototype)
 */
function isObject(val) {
  return Object == val.constructor;
}

以上就是對co源碼的大體分析,不理解的或者有異議的同窗歡迎留言討論。

相關文章
相關標籤/搜索