關(guān)于Flutter.wait操作符的源碼解析

一妆绞、 簡介

flutter中關(guān)于wait操作符恋沃,堪稱神器必搞,但是如果不了解其具體實現(xiàn)原理,就很難巧妙的運用好這個操作符芽唇,所以很多人只會簡單用來收集多個并發(fā)調(diào)用的結(jié)果顾画。

二、參數(shù)介紹

  • wait(Iterable<Future> futures匆笤,{bool eagerError = false, void cleanUp(T successValue)?})
    • Iterable<Future> futures 這個參數(shù)應(yīng)該大多數(shù)人都知道研侣,而且大多數(shù)都只用這一個參數(shù)。就是一個Future的數(shù)組炮捧,你把所有的Future函數(shù)都放這個數(shù)組里庶诡。
    • eagerError 這個參數(shù)很少人用,它用于是否需要立即返回error咆课。eagerError為ture時末誓,這個error是futures中任何一個接口發(fā)生錯誤了,就立即獲得error书蚪。但是不會中斷其它futures中未執(zhí)行完的函數(shù)繼續(xù)執(zhí)行喇澡;當eagerError為false時,會等所有的futures都執(zhí)行完之后殊校,才會把futures中發(fā)生的第一個error發(fā)出來晴玖。
    • void cleanUp(T successValue)?這是一個Fuction,從命名上看为流,是清除的意思呕屎,不看源碼其實很難理解清除什么。這個在實際使用的時候敬察,只有在futures中至少有一個future發(fā)生了error才會執(zhí)行這個方法秀睛。這個方法中可以獲取到所有執(zhí)行成功那部分future的value。
      值得注意的是: Future.wait()這個操作符中所有futures莲祸,當它們發(fā)生錯誤時蹂安,只會返回第一個error椭迎,也就是超過2個接口報錯了,你只能拿到第一個接口的錯誤田盈。

三侠碧、源碼解析

1. 源碼框架
static Future<List<T>> wait<T>(Iterable<Future<T>> futures,
      {bool eagerError = false, void cleanUp(T successValue)?}) {
    final _Future<List<T>> _future = _Future<List<T>>();
    List<T?>? values; // 一個用于存放value的集合,如果是error,則存null
    int remaining = 0; // 可以理解為futures順序執(zhí)行的隊列大小缠黍。
    late Object error; // 用于存放futures中第一個發(fā)生的錯誤
    late StackTrace stackTrace; // 用于存放這個是和error對應(yīng)的錯誤的方法調(diào)用棧
    //這是個內(nèi)部方法,用于處理錯誤
    void handleError(Object theError, StackTrace theStackTrace) {
      remaining--;
      xxx....
      //這里省略部分代碼
      xxx.....
    }

    try {
      for (var future in futures) {//遍歷需要調(diào)用的方法药蜻,給每個future添加then回調(diào)。
        int pos = remaining;//把對應(yīng)的下標記錄下來,再下面的then中需要使用
        future.then((T value) {
          remaining--;
          xxx....
         //這里省略部分代碼
          xxx.....
        }, onError: handleError);
        remaining++;//隊列總數(shù)+1
      }
      if (remaining == 0) { //futures列表為空或者滤馍,直接結(jié)束
        return _future.._completeWithValue(<T>[]);
      }
      values = new List<T?>.filled(remaining, null);//初始化一個大小為remaining的數(shù)組容器拓劝。用于存放所有futures的value
    } catch (e, st) {
     xxx....
     //省略部分代碼
     xxx....
    }
    return _future;
  }
  1. 從代碼大體框架可以看出,首先第一步是按照數(shù)組futures中的順序給每個future添加一個then操作符踱卵,并且創(chuàng)建一個values用來存放所有futures中的value廊驼。并且聲明了兩個局部變量error和stackTrace,用于存放futures發(fā)生的第一個錯誤的error以及方法調(diào)用棧惋砂。
  2. 如果futures數(shù)組為空妒挎,則直接結(jié)束這個Future。
2. 內(nèi)部函數(shù)handleError方法的邏輯
     void handleError(Object theError, StackTrace theStackTrace) {
      remaining--;
      List<T?>? valueList = values;
      if (valueList != null) {
        if (cleanUp != null) {
          for (var value in valueList) {
            if (value != null) {
              // Ensure errors from cleanUp are uncaught.
              T cleanUpValue = value;
              new Future.sync(() {
                cleanUp(cleanUpValue);
              });
            }
          }
        }
        values = null;
        if (remaining == 0 || eagerError) {
          _future._completeError(theError, theStackTrace);
        } else {
          error = theError;
          stackTrace = theStackTrace;
        }
      } else if (remaining == 0 && !eagerError) {
       //省略部分代碼
      }
    }

這部分代碼邏輯也很簡單清晰西饵。

  1. 將所有的values值通過cleanUp的回調(diào)發(fā)送出去酝掩,當所有的values都發(fā)送完畢之后,values賦null值眷柔,
  2. 第二個if以下有3種情況
    a. 如果所有的futures都執(zhí)行完了期虾,將error拋出去,
    b. eagarError為true時驯嘱,將發(fā)生的error直接輸出镶苞,
    c. 以上兩個條件都滿足了,那肯定也error直接拋出去了鞠评。
  3. 如果不需要futures沒有執(zhí)行完茂蚓,且不需要立即把error拋出,則將error緩存下來谢澈。看到這個地方時煌贴,肯定有一個疑問。如果futures中發(fā)生第二個錯誤的時候锥忿,也滿足eagerError為false并且futures還沒執(zhí)行完時牛郑,第二個error信息豈不是會覆蓋之前的緩存,這個和之前定義的error只會輸出第一個有點相違背呀敬鬓。繼續(xù)往下看淹朋。
void handleError(Object theError, StackTrace theStackTrace) {
      remaining--;
      List<T?>? valueList = values;
      if (valueList != null) {
       //省略部分代碼
      } else if (remaining == 0 && !eagerError) {
        _future._completeError(error, stackTrace);
      }
    }

這里把前面if內(nèi)的代碼屏蔽笙各,這樣看著更清晰一點。邏輯很簡單础芍,在某種條件下將error信息拋出杈抢。具體條件如下:
首先 因為第一個錯誤發(fā)生的時候,在if內(nèi)部已經(jīng)將values賦值為null了仑性,所以第二個錯誤發(fā)生的時候惶楼,就會走這個else的邏輯了。
其次诊杆,這個else中又添加了2個條件歼捐。所有的futures沒有執(zhí)行完并且eagerError這個參數(shù)為false(也就是不需要立即拋出error)。這不就是恰巧回答了上面那個疑問么晨汹。
因此豹储, 當?shù)诙€及以后error發(fā)生的時候,都被這個handler自己吃掉了淘这,不會再往外拋剥扣。

3. then函數(shù)中if邏輯
future.then((T value) {
          remaining--;
          List<T?>? valueList = values;
          if (valueList != null) {
            valueList[pos] = value;
            if (remaining == 0) {
              _future._completeWithValue(List<T>.from(valueList));
            }
          } else {
           //省略部分代碼
          }
        }, onError: handleError);

這部分邏輯也很清晰,pos在前面描述過铝穷,記錄了futures的對應(yīng)位置钠怯。就是按照futures的順序?qū)⑺械膙alue添加到values中,沒添加完一個就remaining減一氧骤,直到remaining為0呻疹,則表示所有的future中的value都添加完了,最后把這個values列表通過_completeWithValue下發(fā)一個數(shù)組筹陵。
換個角度說Future.wait().then(value =>{})中獲取到的這個value會是個數(shù)組刽锤,數(shù)組里面所有元素的順序和futures中每個方法的順序是一樣的。

4. then中else的邏輯
future.then((T value) {
          remaining--;
          List<T?>? valueList = values;
          if (valueList != null) {
           //省略部分代碼
          } else {
            if (cleanUp != null && value != null) {
              new Future.sync(() {
                cleanUp(value);
              });
            }
            if (remaining == 0 && !eagerError) {
              _future._completeError(error, stackTrace);
            }
          }
        }, onError: handleError);

當這個values為null時朦佩,將當前的value通過cleanUp直接輸出并思。也就是在前面提到的,cleanUp回調(diào)函數(shù)中可以獲取到對應(yīng)future的value语稠。
前面在handlerError中有提到宋彼,當future中發(fā)生error的時候,如果eagerError為true仙畦,則values就會被賦值為null输涕,所以當發(fā)生error之后所有的value就不會被緩存了,會直接通過cleanUp的方式往外輸出慨畸。

如果eagerError為false時莱坎,

 if (remaining == 0 && !eagerError) {
     _future._completeError(error, stackTrace);
 }

這段代碼就會執(zhí)行,輸出之前緩存的error寸士。

四檐什、結(jié)語

  1. 參數(shù)Iterable futures中所有的future是按順序執(zhí)行的
  2. 當其中一個或者多個future發(fā)生錯誤時碴卧,會根據(jù)eagerError參數(shù)判斷是會否緩存第一個error和它的堆棧信息
    a.eagerError 為ture,發(fā)生錯誤之后就會把之前所有的已經(jīng)執(zhí)行完的future的value乃正,通過cleanUp清除住册,并且在這個回調(diào)函數(shù)中可以獲取到
    b. eagerError 為false,則會把第一個錯誤的error及相關(guān)信息緩存下來瓮具。直到所有future執(zhí)行完了之后荧飞,通過clean Up回調(diào)輸出所有的value,最后輸出error及相關(guān)信息名党。
  3. 至于所有的future都成功執(zhí)行之后垢箕,才會走then回調(diào)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兑巾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子忠荞,更是在濱河造成了極大的恐慌蒋歌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件委煤,死亡現(xiàn)場離奇詭異堂油,居然都是意外死亡,警方通過查閱死者的電腦和手機碧绞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門府框,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人讥邻,你說我怎么就攤上這事迫靖。” “怎么了兴使?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵系宜,是天一觀的道長。 經(jīng)常有香客問我发魄,道長盹牧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任励幼,我火速辦了婚禮汰寓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘苹粟。我一直安慰自己有滑,他們只是感情好,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布六水。 她就那樣靜靜地躺著俺孙,像睡著了一般辣卒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上睛榄,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天荣茫,我揣著相機與錄音,去河邊找鬼场靴。 笑死啡莉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的旨剥。 我是一名探鬼主播咧欣,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼轨帜!你這毒婦竟也來了魄咕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蚌父,失蹤者是張志新(化名)和其女友劉穎哮兰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苟弛,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡喝滞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了膏秫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片右遭。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖缤削,靈堂內(nèi)的尸體忽然破棺而出窘哈,到底是詐尸還是另有隱情,我是刑警寧澤亭敢,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布宵距,位于F島的核電站,受9級特大地震影響吨拗,放射性物質(zhì)發(fā)生泄漏满哪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一劝篷、第九天 我趴在偏房一處隱蔽的房頂上張望哨鸭。 院中可真熱鬧,春花似錦娇妓、人聲如沸像鸡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽只估。三九已至志群,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蛔钙,已是汗流浹背锌云。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吁脱,地道東北人桑涎。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像兼贡,于是被迫代替她去往敵國和親攻冷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348