最近用Flutter寫了個簡單的“爬蟲”的東西,就是要不斷地請求某些介面,然後讀取資料就可以了。之前只是會簡單地await和async來試用Future,後面發現只會簡單的這種方式不行,於是去學習了Future的其他用法,還是有點收穫的,把Future的用法都學了一下,這裡做個筆記。
哈哈,別問我為什麼沒用python去搞。。。剛好用的電腦沒裝環境並且python的API沒那麼熟,就有了個想法,反正都能實現,就用Flutter玩一下吧,可能後面還可以搞個視覺化介面玩玩。
Dart的訊息迴圈機制
推薦文章:Dart與訊息迴圈機制
這裡簡單說下Dart的事件迴圈,更加具體的話,可以看推薦的這篇文章。
類似Android的Handler/Looper機制,Dart也有相應的訊息迴圈機制。
- 一個Dart應用中有一個訊息迴圈和兩個佇列,一個是event佇列,一個是microtask佇列。
- 優先順序問題:microtask佇列的優先順序是最高的,當所有microtask佇列執行完之後才會從event佇列中讀取事件進行執行。
- microtask佇列中的event,
- event佇列包含所有的外來事件:I/O,mouse events,drawing events,timers,isolate之間的message等。
- 一般Future建立的事件是屬於event佇列的(利用Future.microtask方法例外),所以建立一個Future後,會插入到event佇列中,順序執行。
Future的介紹
在寫程式的過程中,肯定會有一部分比較耗時程式碼是需要非同步執行的。比如網路操作,我們需要非同步去請求資料,並且還需要處理請求成功和請求失敗的兩種情況。
在Flutter中,使用Future來執行耗時操作,表示在未來會返回某個值,並可以使用then()方法和catchError()來註冊callback來監聽Future的處理結果。
如下面所示,原始碼中對Future的解釋,以及簡單的一個例子。then()方法和catchError()返回的其實也是一個Future型別物件,所以可以寫成鏈式呼叫的形式。
An object representing a delayed computation.
Future<int> future = getFuture();
future.then((value) => handleValue(value))
.catchError((error) => handleError(error));
複製程式碼
Flutter的狀態
一個Future物件會有以下兩種狀態
- pending:表示Future物件的計算過程仍在執行中,這個時候還沒有可以用的result。
- completed:表示Future物件已經計算結束了,可能會有兩種情況,一種是正確的結果,一種是失敗的結果。
建立Future
注意:首先Future是個泛型類,可以指定型別。如果沒有指定相應型別的話,預設返回是Future型別的。
Future createFuture()async{
return 1;
}
var future=createFuture();
print(future.runtimeType);。//輸出結果為Future<dynamic>
複製程式碼
Future常用的建立有以下幾種方式。
基本的用法
factory Future(FutureOr computation())
Future的構造方法,建立一個基本的Future
var future = Future(() {
print("哈哈哈");
});
print("111");
//111
//哈哈哈
複製程式碼
你會發現,結果是列印“哈哈哈”字串是在後面才輸出的,原因是預設future是非同步執行的。如果改成下面的程式碼,在future前面加上await關鍵字,則會先列印“哈哈哈”字串。await會等待直到future執行結束後,才繼續執行後面的程式碼。
這個跟上面的事件迴圈是有關係的,轉發大佬的解釋:main方法中的普通程式碼都是同步執行的,所以肯定是main列印先全部列印出來,等main方法結束後會開始檢查microtask中是否有任務,若有則執行,執行完繼續檢查microtask,直到microtask列隊為空。所以接著列印的應該是microtask的列印。最後會去執行event佇列。
var future = await Future(() {
print("哈哈哈");
});
print("111");
//哈哈哈
//111
複製程式碼
Future.value()
建立一個返回指定value值的Future
var future=Future.value(1);
print(future);
//Instance of 'Future<int>'
複製程式碼
Future.delayed()
建立一個延遲執行的future。 例如下面的例子,利用Future延遲兩秒後可以列印出字串。
var futureDelayed = Future.delayed(Duration(seconds: 2), () {
print("Future.delayed");
return 2;
});
複製程式碼
高階的用法
Future.foreach(Iterable elements, FutureOr action(T element))
根據某個集合,建立一系列的Future,並且會按順序執行這些Future。 比如下面的例子,根據{1,2,3}建立3個延遲對應秒數的Future。執行結果為1秒後列印1,再過2秒列印2,再過3秒列印3,總時間為6秒。
Future.forEach({1,2,3}, (num){
return Future.delayed(Duration(seconds: num),(){print(num);});
});
複製程式碼
Future.wait ( Iterable<Future> futures,{bool eagerError: false, void cleanUp(T successValue)})
用來等待多個future完成,並收集它們的結果。那這樣的結果就有兩種情況了:
- 如果所有future都有正常結果返回:則future的返回結果是所有指定future的結果的集合
- 如果其中一個future有error返回:則future的返回結果是第一個error的值。
比如下面的例子,也是建立3個延遲對應秒數的Future。結果是總時間過了3秒後,才輸出[1, 2, 3]的結果。可以與上面的例子對比一下,一個是順序執行多個Future,一個是非同步執行多個Future。
var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);
var future2 =
new Future.delayed(new Duration(seconds: 2), () => 2);
var future3 = new Future.delayed(new Duration(seconds: 3), () => 3);
Future.wait({future1,future2,future3}).then(print).catchError(print);
//執行結果: [1, 2, 3]
複製程式碼
將future2和future3改為有error丟擲,則future.wait的結果是這個future2的error值。
var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);
var future2 =new Future.delayed(new Duration(seconds: 2), () => throw “throw error2");
var future3 = new Future.delayed(new Duration(seconds: 3), () => throw “throw error3");
Future.wait({future1,future2,future3}).then(print).catchError(print);
//執行結果: throw error2
複製程式碼
Future.any(futures)
返回的是第一個執行完成的future的結果,不會管這個結果是正確的還是error的。(感覺這個的使用場景沒幾個的樣子,想不到有什麼場景要用到這個。)
例如下面的例子,使用Future.delayed()延遲建立了三個不同的Future,第一個完成返回的是延遲1秒的那個future的結果。
Future
.any([1, 2, 5].map(
(delay) => new Future.delayed(new Duration(seconds: delay), () => delay)))
.then(print)
.catchError(print);
複製程式碼
Future.doWhile()
類似java中doWhile的用法,重複性地執行某一個動作,直到返回false或者Future,退出迴圈。
使用場景:適用於一些需要遞迴操作的場景。
比如我就是要爬某個平臺的商品分類的資料,那每個分類下面又有相應的子分類。那我就得拿對應父級分類的catId去請求介面,拿到這個分類下面的所有子分類。同樣的,對所有的子分類,也要進行一樣的操作,遞迴直到這個分類是一個葉子節點,也就是該節點下面沒有子分類(有個leaf為true的欄位)。哈哈,程式碼我就沒貼了,其實也就是一個遞迴操作,直到滿足條件退出future。
例如下面的例子,生成一個隨機數進行等待,直到十秒之後,操作結束。
void futureDoWhile(){
var random = new Random();
var totalDelay = 0;
Future
.doWhile(() {
if (totalDelay > 10) {
print('total delay: $totalDelay seconds');
return false;
}
var delay = random.nextInt(5) + 1;
totalDelay += delay;
return new Future.delayed(new Duration(seconds: delay), () {
print('waited $delay seconds');
return true;
});
})
.then(print)
.catchError(print);
}
//輸出結果:
I/flutter (11113): waited 5 seconds
I/flutter (11113): waited 1 seconds
I/flutter (11113): waited 3 seconds
I/flutter (11113): waited 2 seconds
I/flutter (11113): total delay: 12 seconds
I/flutter (11113): null
複製程式碼
Future.microtask(FutureOr computation())
建立一個在microtask佇列執行的future。
在上面講過,microtask佇列的優先順序是比event佇列高的,而一般future是在event佇列執行的,所以Future.microtask建立的future會優先於其他future進行執行。
例如下面的程式碼,
Future((){
print("Future event 1");
});
Future((){
print("Future event 2");
});
Future.microtask((){
print("microtask event");
});
//輸出結果
//microtask event
//Future event 1
//Future event 2
複製程式碼
處理Future的結果
Flutter提供了下面三個方法,讓我們來註冊回撥,來監聽處理Future的結果。
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
Future<T> catchError(Function onError, {bool test(Object error)});
Future<T> whenComplete(FutureOr action());
複製程式碼
Future.then()
用於在Future完成的時候新增回撥。
注意:then()的返回值也是一個Future物件。所以我們可以使用鏈式的方法去使用Future,將前一個Future的輸出結果作為後一個Future的輸入,可以寫成鏈式呼叫。
例如下面的程式碼,將前面的future結果作為後面Future的輸入。
Future.value(1).then((value) {
return Future.value(value + 2);
}).then((value) {
return Future.value(value + 3);
}).then(print);
//列印結果為6
複製程式碼
Future.cathcError()
註冊一個回撥,來處理有error的Future
new Future.error('boom!').catchError(print);
複製程式碼
與then方法中的onError的區別:then方法裡面也有個onError的引數,也可以用來處理錯誤的Future。
兩者的區別,onError只能處理當前的錯誤Future,而不能處理其他有錯誤的Future。catchError可以捕獲到Future鏈中丟擲的所有錯誤。
所以通常的做法是使用catchError來捕捉Future中的所有錯誤,不建議使用then方法中的onError方法。不然每個future的then方法都要加上onError回撥的話,就比較麻煩了,而且程式碼看起來也是有點亂。
下面是兩個捕捉錯誤的例子。
在那個丟擲錯誤的future的then方法裡新增onError回撥的話,onError會優先被呼叫
Future.value(1).then((value) {
return Future.value(value + 2);
}).then((value) {
throw "error";
}).then(print, onError: (e) {
print("onError find a error");
}).catchError((e) {
print("catchError find a error");
});
//輸出結果為onError find a error
複製程式碼
使用catchError來監聽鏈式呼叫Future裡面丟擲來的錯誤。
Future.value(1).then((value) {
throw "error";
}).then((value) {
return Future.value(3);
}).then(print).then((value) {
}).catchError((e) {
print("catchError find a error");
});
//輸出結果為catchError find a error"
複製程式碼
Future.whenComplete
類似Java中的finally,Future.whenComplete總是在Future完成後呼叫,不管Future的結果是正確的還是錯誤的。
注意:Future.whenComplete的返回值也是一個Future物件。
Future.delayed(Duration(seconds: 3),(){
print("哈哈");
}).whenComplete((){
print("complete");
});
//哈哈
//complete
複製程式碼
最常用的async和await
哈哈,上面說了一些Future的用法,其實有些可能並不會很常用,知道有這個用法就行了。
有個問題,如果有多個Future連結在一起的話,靠,程式碼可能會變得難以閱讀。
所以,可以使用async和await來使用Future,使程式碼看起來像是同步的程式碼,但實際上它們還是非同步執行的。
最常用的還是async和await來使用Future。
注意:await只能在async函式出現。 async函式,返回值是一個Future物件,如果沒有返回Future物件,會自動將返回值包裝成Future物件。 捕捉錯誤,一般是使用try/catch機制來做異常處理。 await 一個future,可以拿到Future的結果,直到拿到結果,才執行下一步的程式碼。
比如下面的例子,建立一個延遲3秒並返回結果為1的Future,使用await來獲取到future的值。
void main() async {
var future = new Future.delayed(new Duration(seconds: 3), () {
return 1;
});
var result = await future;
print(result + 1);
}
//輸出為2
複製程式碼
下面的程式碼結構,跟java一樣的寫法,使用try-catch-finally來捕捉錯誤。
try {
var result1 = await Future.value(1);
print(result1);//輸出1
var result2 = await Future.value(2);
print(result2);//輸出2
} catch (e) {
//捕捉錯誤
} finally {
}
複製程式碼
總結
- 建立Future的幾種方法
- 用then、catchError、whenComplete來處理Future的結果
- async和await的使用
下一步
多學習多整理。靠,又懶了好長一段時間了。。。