Flutter進階篇(4)-- Flutter的Future異步詳解

Flutter中的異步其實就是用的Dart里面的Future,then函數,回調catchError這些東西。下面舉例詳細解答一下使用過程和遇到的一些問題,讓大家更好的明白異步流程。

本文首發在公眾號Flutter那些事,歡迎大家多多關注。


Flutter系列博文鏈接 ↓:

工具安裝:

Flutter基礎篇:

Flutter進階篇:

Dart語法系列博文鏈接 ↓:

Dart語法基礎篇:

Dart語法進階篇:


說明:本文中的所有函數的引用在main函數中:

main() async {
  testFuture();
  testFuture2();
  testFutureCreate1();
  testFutureCreate2();
  testFutureCreate3();
  testThen1();
  testThen2();
  testThen3();
  testThen4();
  testAll();
}

一、認識Future

1.創建Future

void testFuture(){
  Future future = new Future(() => null);
  future.then((_){
    print("then");
  }).then((){
    print("whenComplete");
  }).catchError((_){
    print("catchError");
  });
} 

這里的執行結果是:

then
whenComplete

Futue直接new就可以了。我這里沒有具體的返回數據,所以就用匿名函數代替了, Future future = new Future(() => null); 相當于 Future<Null> future = new Future(() => null); 泛型如果為null可以省略不寫,為了便于維護和管理,開發中建議加上泛型。

我們也可以直接在創建Future的時候直接輸出一些內容,例如:

void testFuture2(){
  Future f1 = new Future(() => print("1"));
  Future f2 = new Future(() => print("2"));
  Future f3 = new Future(() => print("3"));
}

輸出結果是:

1
2
3

2.Future相關回調函數

future里面有幾個函數:
then:異步操作邏輯在這里寫。
whenComplete:異步完成時的回調。
catchError:捕獲異常或者異步出錯時的回調。

因為這里面的異步操作過程中沒有遇到什么錯誤,所以catchError回調不會調用。

在我們平時開發中我們是這樣用的,首先給我們的函數后面加上async關鍵字,表示異步操作,然后函數返回值寫成Future,然后我們可以new一個Future,邏輯前面加上一個await關鍵字,然后可以使用future.then等操作。下面是一個示例操作,為了方便演示,這里的返回值的null。平時開發中如果請求網絡返回的是json,我們可以把泛型寫成String;泛型也可能是實體類(entity/domain),不過要做json轉實體類相關操作。

Future asyncDemo() async{
    Future<Null> future = new Future(() => null);
    await  future.then((_){
      print("then");
    }).then((){
      print("whenComplete");
    }).catchError((_){
      print("catchError");
    });
}


二、創建多個Future的執行步驟

1.我們這里創建3個Future,我們看看執行過程:

void testFutureCreate1(){
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => null);
  // 都是異步 空實現 順序進行
  f1.then((_) => print("1"));
  f2.then((_) => print("2"));
  f3.then((_) => print("3"));
}

我們可以看到執行結果是:

1
2
3

2.那么我們猜想是不是按照創建Future對象的先后順序依次執行? 接下來我們打亂順序,再試試看!

void testFutureCreate2(){
  Future f2 = new Future(() => null);
  Future f1 = new Future(() => null);
  Future f3 = new Future(() => null);
  f1.then((_) => print("1"));
  f2.then((_) => print("2"));
  f3.then((_) => print("3"));
}

我們可以看到輸出結果是: 2 1 3 和我們創建Future對象的先后順序完全一致。

2
1
3

3.接下來我們繼續猜想打亂then的調用先后順序試試看?會不會有影響?

void testFutureCreate3(){

  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => null);

  f3.then((_) => print("3"));
  f1.then((_) => print("1"));
  f2.then((_) => print("2"));

}

我們可以看到結果為1 2 3,和我們調用then的先后順序無關。:

1
2
3

4.【結論】: 創建多個Future,執行順序和和創建Future的先后順序有關,如果只是單獨的調用then,沒有嵌套使用的話,和調用then的先后順序無關。


三、then函數嵌套使用的執行步驟

當then回調函數里面還有then回調的時候,這時候的流程跟前面就不太一樣了,也是一個大坑,也是面試經常會被問到的一個知識點。

1.我們一開始就執行f1的then回調,接著是f2的then回調里面,包含一個f1的then回調,最后是f3的then回調。示例如下:

void testThen1() {
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => null);

  f1.then((_) => print("f1 -> f1"));
  // f2 then 異步回調里面還有異步回調
  f2.then((_) {
    print("f2 -> f2");
    f1.then((_) => print("f2.then -> f1"));
  });
  f3.then((_) => print("f3 -> f3"));
}

我們可以看到執行結果如下:

f1 -> f1
f2 -> f2
f2.then -> f1
f3 -> f3

2.接下來我們交換一下調用then的順序。我們發現結果是一樣的:

void testThen2() {
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => null);

  f2.then((_) {
    print("f2 -> f2");
    f1.then((_) => print("f2.then -> f1"));
  });
  f1.then((_) => print("f1 -> f1"));
  f3.then((_) => print("f3 -> f3"));
}

結果還是一樣的:

f1 -> f1
f2 -> f2
f2.then -> f1
f3 -> f3

3.接下來我們調整一下。

void testThen3() {
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => null);

  f1.then((_) => print("f1 -> f1"));
  f3.then((_) {
    print("f3 -> f3");
    f1.then((_) => print("f3.then -> f1"));
  });
  f2.then((_) => print("f2 -> f2"));
}

運行結果是:

f1 -> f1
f2 -> f2
f3 -> f3
f3.then -> f1

這里再次證明了上面我的猜想:執行順序和和創建Future的先后順序有關,如果有多個then嵌套執行,先執行外面的then,然后執行里面的then。

4. 接下來我們在then內部創建一個Future 看看執行順序如何?

void testThen4() {
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => null);

  f1.then((_) => print("f1 -> f1"));
  f3.then((_) {
    print("f3 -> f3");
    new Future(() => print("f3.then -> new Future"));
    f1.then((_) => print("f3.then -> f1"));
  });
  f2.then((_) => print("f2 -> f2"));
}

執行結果如下,我們可以看到then內部創建的Future要等到then執行完了,最后再去執行的:

f1 -> f1
f2 -> f2
f3 -> f3
f3.then -> f1
f3.then -> new Future

5.【結論】: 首先執行順序和創建Future的先后順序有關,如果遇到多個 then 嵌套,先執行外面的 then,然后再執行里面的then,如果then里面還有創建Future,要等到then執行完畢,之后執行Future。


四、綜合示例

void testAll() {
  Future f3 = new Future(() => null);
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f4 = new Future(() => null);
  Future f5 = new Future(() => null);

  f3.then((_) => print("f3.then -> f3"));
  f2.then((_) => print("f2.then -> f2"));
  f4.then((_) => print("f4.then -> f4"));

  f5.then((_) {
    print("f5.then -> f5");
    new Future(() => print("f5.then -> new Future"));
    f1.then((_) {
      print("f1.then -> f1");
    });
  });
}

根據上文總結的特點,我們可以不用運行也能推斷出輸出結果:

首先按照Future創建順序應該是 f3 f1 f2 f4 f5依次執行。
我們看到then函數的調用情況,f3先調用,所以它應該先輸出。
緊接著是f2調用了,所以f2輸出。
緊接著f4調用了,f4應該輸出。
緊接著是f5調用then函數,這個比較特殊,它是then函數的嵌套使用,首先是一個打印語句,直接輸出,然后是new Future函數,它應該等then執行完畢再去執行,所以這里會去找下面的f1.then里面的邏輯,然后輸出f1,到此f5.then都執行完畢,然后就是執行new Future里面的邏輯(如果沒有內部嵌套 then的話,它就會直接輸出。)

為了驗證我們的猜想,我們打印一下輸出結果,果然我們的證明是正確的。

f3.then -> f3
f2.then -> f2
f4.then -> f4
f5.then -> f5
f1.then -> f1
f5.then -> new Future

五、我們來看看Future的源碼說明文檔

我們重點看看then函數的文檔說明:

then注冊在 Future 完成時調用的回調。
當這個 Future 用一個 value 完成時,將使用該值調用onValue回調。
如果 Future已經完成,則不會立即調用回調,而是將在稍后的microtask(微任務)中調度。
如果回調返回Future,那么then返回的future將與callback返回的future結果相同。

onError回調必須接受一個參數或兩個參數,后者是[StackTrace]。

如果onError接受兩個參數,則使用錯誤和堆棧跟蹤時調用它,否則僅使用錯誤對象時候調用它。

onError回調必須返回一個可用于完成返回的future的值或future,因此它必須是可賦值給FutureOr <R>的東西。

返回一個新的Future,該Future是通過調用onValue(如果這個Future是通過一個value完成的)或'onError(如果這個Future是通過一個error完成的)的結果完成的。

如果調用的回調拋出異常,返回的future將使用拋出的錯誤和錯誤的堆棧跟蹤完成。在onError的情況下,如果拋出的異常與onError的錯誤參數“相同(identical)”,則視為重新拋出,并使用原始堆棧跟蹤替代

如果回調返回Future,則then返回的Future將以與回調返回的Future相同的結果完成。

如果未給出onError,并且后續程序走了剛出現了錯誤,則錯誤將直接轉發給返回的Future。

在大多數情況下,單獨使用catchError更可讀,可能使用test參數,而不是在單個then調用中同時處理valueerror。

請注意,在添加監聽器(listener)之前,future不會延遲報告錯誤。如果第一個thencatchError調用在future完成后發生error,那么error將報告為未處理的錯誤。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1.ios高性能編程 (1).內層 最小的內層平均值和峰值(2).耗電量 高效的算法和數據結構(3).初始化時...
    歐辰_OSR閱讀 29,611評論 8 265
  • 你不知道JS:異步 第三章:Promises 在第二章,我們指出了采用回調來表達異步和管理并發時的兩種主要不足:缺...
    purple_force閱讀 2,115評論 0 4
  • 【正文】 如果我們希望孩子在成長的過程中對自己有信心,我們就需要利用每個機會強調他們積極的一面,避免使用貶低性的言...
    趙金芳Kevin閱讀 246評論 3 1
  • 《是否還在》 情人離開了 我回到心房 感情還在 流行小三了 我跑回家 老婆還在 霧霾又起了 我跑會內蒙古 草原還在...
    白清風閱讀 211評論 0 1
  • “為什么我總找不到好的工作?” “為什么我這么努力,卻不如那些隨便混混的‘黃馬褂’們成績更好?” “為什么我不是優...
    假文藝中年人閱讀 333評論 0 1