Flutter篇之你真的會使用Future嗎?

最近用Flutter寫了個簡單的「爬蟲」的東西,就是要不斷地請求某些接口,而後讀取數據就能夠了。以前只是會簡單地await和async來試用Future,後面發現只會簡單的這種方式不行,因而去學習了Future的其餘用法,仍是有點收穫的,把Future的用法都學了一下,這裏作個筆記。java

哈哈,別問我爲何沒用python去搞。。。恰好用的電腦沒裝環境而且python的API沒那麼熟,就有了個想法,反正都能實現,就用Flutter玩一下吧,可能後面還能夠搞個可視化界面玩玩。python

Dart的消息循環機制

推薦文章:Dart與消息循環機制bash

這裏簡單說下Dart的事件循環,更加具體的話,能夠看推薦的這篇文章。網絡

相似Android的Handler/Looper機制,Dart也有相應的消息循環機制。dom

  • 一個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的處理結果。async

以下面所示,源碼中對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對象會有如下兩種狀態oop

  • 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的使用

下一步

多學習多整理。靠,又懶了好長一段時間了。。。

相關文章
相關標籤/搜索