一妆绞、 簡介
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;
}
- 從代碼大體框架可以看出,首先第一步是按照數(shù)組futures中的順序給每個future添加一個then操作符踱卵,并且創(chuàng)建一個values用來存放所有futures中的value廊驼。并且聲明了兩個局部變量error和stackTrace,用于存放futures發(fā)生的第一個錯誤的error以及方法調(diào)用棧惋砂。
- 如果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) {
//省略部分代碼
}
}
這部分代碼邏輯也很簡單清晰西饵。
- 將所有的values值通過cleanUp的回調(diào)發(fā)送出去酝掩,當所有的values都發(fā)送完畢之后,values賦null值眷柔,
- 第二個if以下有3種情況
a. 如果所有的futures都執(zhí)行完了期虾,將error拋出去,
b. eagarError為true時驯嘱,將發(fā)生的error直接輸出镶苞,
c. 以上兩個條件都滿足了,那肯定也error直接拋出去了鞠评。 - 如果不需要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é)語
- 參數(shù)Iterable futures中所有的future是按順序執(zhí)行的
- 當其中一個或者多個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)信息名党。 - 至于所有的future都成功執(zhí)行之后垢箕,才會走then回調(diào)。