[前端漫談_2] 從 Dva 的 Effect 到 Generator + Promise 實現異步編程

你能學到什麼

  • 如何使用 Generator + Promise 實現異步編程
  • 異步編程的原理解析

前言

結合 上一篇文章 ,咱們來聊聊 Generatorjavascript

基礎原理

說到異步編程,你想到的是asyncawait ,但那也只是 Generator 的語法糖而已。dva 中有一個 Effect 的概念,它就是使用 Generator 來解決異步請求的問題,咱們也來聊一聊 Generator + Promise 如何異步編程:html

開始以前,咱們須要瞭解一些基本的概念:前端

  • Generator做爲 ES6 中使用協程的解決方案來處理異步編程的具體實現,它的特色是: Generator 中可使用 yield 關鍵字配合實例 gen 調用 next() 方法,來將其內部的語句分割執行。 簡言之 : next() 被調用一次,則 yield 語句被執行一句,隨着 next() 調用, yield 語句被依次執行。
  • Promise表示一個異步操做的最終狀態(完成或失敗),以及其返回的值。參考Promise-MDN

因此,異步編程使用 GeneratorPromise 來實現的原理是什麼呢?java

  1. 由於 Generator 自己 yield 語句是分離執行的,因此咱們利用這一點,在 yield 語句中返回一個 Promise 對象
  2. 首次調用 Generator 中的 next() 後, 假設返回值叫 result ,那麼此時 result.value 就是咱們定義在 yield 語句中的 Promise 對象

注意:在這一步,咱們已經把原來的執行流程暫停,轉而執行 Promise 的內容,已經實現了控制異步代碼的執行,由於此時咱們若是不繼續執行 next()generator 中位於當前被執行的 yield 後面的內容,將不會繼續執行,這已經達到了咱們須要的效果git

  1. 接下來咱們就是在執行完當前 Promise 以後,讓代碼繼續往下執行,直到遇到下一個 yield 語句:

    這一步是最關鍵的 因此咱們怎麼作呢:github

    步驟1: 在當前的 Promisethen() 方法中,繼續執行 gen.next()
    編程

    步驟2: 當 gen.next() 返回的結果 result.done === true 時,咱們拿到 result.value【也就是一個新的 Promise 對象】再次執行而且在它的then() 方法中繼續上面的步驟1,直至 result.done === false 的時候。這時候調用 resolve() 使 promise 狀態改變,由於全部的 yield 語句已經被執行完。promise

    • 步驟1 保證了咱們能夠走到下一個 yield 語句
    • 步驟2 保證了下一個 yield 語句執行完不會中斷,直至 Generator 中的最後一個 yield 語句被執行完。

流程示意圖:app

具體實現

co 是著名大神 TJ 實現的 Generator 的二次封裝庫,那麼咱們就從 co庫中的一個demo開始,瞭解咱們的整個異步請求封裝實現:
co(function*() {
    yield me.loginAction(me.form);
    ...
});

在這裏咱們引入了co庫,而且用co來包裹了一個generator(生成器)對象。

接下來咱們看下co對於包裹起來的generator作了什麼處理異步

function co(gen) {
  // 1.獲取當前co函數的執行上下文環境,獲取到參數列表
  var ctx = this;
  var args = slice.call(arguments, 1);
  // 2.返回一個Promise對象
  return new Promise(function(resolve, reject) {
    //  判斷而且使用ctx:context(上下文環境)和arg:arguments(參數列表)初始化generator而且複製給gen
    // 注意:
    // gen = gen.apply(ctx, args)以後
    // 咱們調用 gen.next() 時,返回的是一個指針,實際的值是一個對象
    // 對象的形式:{done:[false | true], value: ''}
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    // 當返回值不爲gen時或者gen.next的類型不爲function【實際是判斷是否爲generator】時
    // 當前promise狀態被設置爲resolve而結束
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
    // 不然執行onFulfilled()
    onFulfilled();
  });
}

總結一下這裏發生了什麼

  1. 返回一個 promise
  2. promise 中將被包裹的 generator 實例化爲一個指針,指向 generator 中第一個 yield 語句
  3. 判斷 generator 實例化出來的指針是否存在:若是沒有 yield 語句則指針不存在
    判斷指針 gen.next() 方法是否爲 function :若是不爲 function 證實沒法執行 gen.next()
    條件有一項不知足就將 promise 的狀態置爲 resolve
    不然執行 onFulfilled()

接下來咱們看下 onFulfilled() 的實現

function onFulfilled(res) {
      // 在執行onFulfilled時,定義了一個ret來儲存gen.next(res)執行後的指針對象
      var ret;
      try {
        ret = gen.next(res);
      // 在這裏,yield語句拋出的值就是{value:me.loginAction(me.form), done:false}
      } catch (e) {
        return reject(e);
      }
    // 將ret對象傳入到咱們定義在promise中的next方法中
      next(ret);
      return null;
    }

總結一下,onFulfilled 最主要的工做就是

  1. 執行 gen.next() 使代碼執行到 yield 語句
  2. 將執行後返回的結果傳入咱們自定義的 next() 方法中

那麼咱們再來看 next() 方法

function next(ret) {
    // 進入next中首先判斷咱們傳入的ret的done狀態:
    // 狀況1:ret.done = true 表明咱們這個generator中全部yield語句都已經執行完。
    // 那麼將ret.value傳入到resolve()中,promise的狀態變成解決,整個過程結束。
      if (ret.done) return resolve(ret.value);
    // 狀況2:當前ret.done = false 表明generator還未將全部的yield語句執行完,那麼這時候
    // 咱們把當前上下文和ret.value傳入toPromise中,將其轉換爲對應的Promise對象`value`
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
    // 當value確實是一個promise對象的時候,return value.then(onFulfilled,onRejected)
    // 咱們從新進入到了generator中,執行下一條yield語句
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }

總結一下,next 主要工做

  1. 判斷上一次 yield 語句的執行結果
  2. yieldresultvalue 值【其實就是咱們要異步執行的 Promise
  3. 執行 valuethen 方法,從新進入到 onFulfilled 方法中,而在 onFulfilled 中,咱們又將進入到當前方法,如此循環的調用,實現了 generatorPromise 的執行切換,從而實現了 Promise 的內容按照咱們所定義的順序執行。

有同窗可能對這裏的 toPromise 方法有一些疑惑,我先把代碼貼出來

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

其實這個函數作的事情就是,根據不一樣的類型進行轉換,使得最後輸出的類型都是一個 Promise。那具體的轉換細節,你們能夠參考co庫的源碼

至此實現異步操做的控制。

最後

這裏是 Dendoink ,奇舞週刊原創做者,掘金 [聯合編輯 / 小冊做者] 。
對於技術人而言:技 是單兵做戰能力,術 則是運用能力的方法。駕輕就熟,出神入化就是 藝 。在前端娛樂圈,我想成爲一名出色的人民藝術家。掃碼關注公衆號 前端惡霸 我在這裏等你:

相關文章
相關標籤/搜索