[Flutter]Dart Future詳解

大師兄要瀟灑發表於2020-05-18

在客戶端的很多操作需要非同步執行,使用callback處理非同步,很容易寫出回撥地獄(Callback hell)程式碼,導致難以維護。所以在Web端出現了Promise,Android有RxJava,可以使用這些工具,將橫向巢狀的callback程式碼改為縱向擴充套件,更符合人類的直覺,也更加容易維護。配合async,await更是能以同步的方式寫非同步程式碼。

Dart也同樣提供了處理非同步的工具Future,和JavaScript中的Promise十分相似,有Promise使用經驗的人使用Future時會感到十分熟悉。

An object representing a delayed computation.

Future是一個表示延遲計算的物件。代表一些計算將非同步進行,並在將來獲取到計算的結果。 Future有兩種狀態:PendingCompletePending表示任務正在執行中,還未獲取到結果;Complete表示任務已經執行完,並且有結果。 結果有兩種值:成功(succeed)或者失敗(failed), 我們可以使用回撥函式來獲取結果值。

事件迴圈

和其他客戶端開發框架類似(JS中的EventLoop, Android中的Looper,iOS中的RunLoop),Dart中也有一個無線迴圈的事件迴圈,包含兩個佇列: microtask佇列event佇列。Dart會不斷地迴圈訪問這兩個佇列,從中取出任務來執行。microtask佇列的優先順序高於event佇列,如果microtask佇列不為空,就從中取出任務執行。當microtask佇列為空時,才會訪問event佇列

[Flutter]Dart Future詳解

通過scheduleMicrotask方法可以向microtask佇列中新增任務,而UI事件、Timer等操作和Future的預設構造方法都是新增到event佇列中。

預設構造方法

Future構造方法

通過Future的預設構造方法可以建立一個Future物件,。預設構造方法的簽名如下

factory Future(FutureOr<T> computation())
複製程式碼

引數型別是FutureOr<T> computation(),表示返回值是FutureOr<T>型別的函式。可以根據該函式建立一個Future物件。而FutureOr<T>是一個union型別,代表引數函式的返回值,可以是T型別,也可以是一個Future<T>型別。

通過這個方法建立的Futurecomputation函式會被新增到event佇列中執行。

處理結果

then

建立完成Future物件後,可以通過then方法接收Future的結果。

Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
複製程式碼

then方法接收兩個回撥函式引數,onValue接收成功回撥,可選的onError接收失敗回撥。

var future = Future(() {
   //do compution
    return "future value";
  });

future.then((value) {
    print("onValue: $value");
}, onError: (error) {
    print("onError $error");
});
複製程式碼

then方法的返回值也是一個Future,可以繼續對這個物件呼叫then方法,註冊接收結果的函式。後註冊then函式接收的就是前一個then函式的返回值,如果前一個函式沒有返回值,後面就接收到null值。 下面程式碼將會輸出
onValue: future value
onValue: next value

var future = Future(() {
   //do compution
    return "future value";
  });

future.then((value) {
    print("onValue: $value");
    return "next value";
}, onError: (error) {
    print("onError $error");
}).then((value) {
    print("onValue: $value");
});
複製程式碼

then方法的onValue回撥函式的返回值為FutureOr<R>,代表該回撥函式可以返回一個Future物件,或者返回一個T型別的值。如果是返回的是Future物件,那麼後面通過then註冊的回撥函式將收到該Future的結果。 下面程式碼將會輸出
onValue: future value
onValue: request value

var future = Future(() {
   //do compution
    return "future value";
  });

future.then((value) {
    print("onValue: $value");
    var otherFuture = Future(() {
        //do request
        return "request value";
    });
    return otherFuture;
}, onError: (error) {
    print("onError $error");
}).then((value) {
    print("onValue: $value");
});
複製程式碼

通過這種方式 ,就可以發起多個非同步網路請求,從上到下順序執行,而不是通過callback進行橫向巢狀。

catchError

Future沒有正確的獲取到結果發生異常時,除了在then方法中註冊onError回撥外,還可通過catchError方法監聽異常。

Future<T> catchError(Function onError, {bool test(Object error)});
複製程式碼

下面程式碼將會輸出
catchError Exception: exceptin occured

var future = Future(() {
     //do compution
     throw Exception("exception occured");
  });
  future.then((value){
    print("onValue: $value");
  }).catchError((error) {
    print("catchError $error");
  },);
複製程式碼

catchError方法還有一個可以選的test函式引數。當發生異常時,會首先呼叫test函式,如果該函式返回false,異常將不會被catchError函式處理,會繼續傳遞下去;如果test函式返回ture,catchError函式會處理該異常。如果未提供test函式,預設處理為true。

通過then方法的onError引數和catchError方法處理異常有什麼不同呢?

這兩種方式主要的區別是,通過catchError方法可以捕獲到前一個then方法onValue函式中的異常,而通過onError函式方法,無法捕獲同一個then方法onValue函式中的異常。

var future = Future(() {
     return "future value";
  });
  future.then((value){
    print("onValue: $value");
    throw Exception("exception occured");
  }, onError: (error) {
    print("onError $error");
  }).catchError((error) {
    print("catchError $error");
  });
複製程式碼

上面程式碼將會輸出
onValue: future value
catchError Exception: exception occured
異常被catchError捕獲,而onError沒有被呼叫。

whenComplete方法

Future物件還有一個whenComplete方法,當該Future處於完成狀態時,通過該方法註冊的回撥會被呼叫,無論結果是成功還是失敗,相當與finally程式碼塊。 whenComplete方法的引數型別也是FutureOr型別,返回值為Future<T>型別。表示在該方法中可以返回一個值或者是Future物件,在whenComplete函式後,可以再呼叫thencatchError等方法,但是後面註冊回撥函式並不能獲取到whenComplete中返回的值,而是whenComplete前的值。 該邏輯等效於下面的程式碼

  Future<T> whenComplete(action()) {
       return this.then((v) {
         var f2 = action();
         if (f2 is Future) return f2.then((_) => v);
         return v
       }, onError: (e) {
         var f2 = action();
         if (f2 is Future) return f2.then((_) { throw e; });
         throw e;
       });
  }
複製程式碼

timeout方法

Future<T> timeout(Duration timeLimit, {FutureOr<T> onTimeout()})
複製程式碼

timeout方法建立一個新的Future物件,接收一個Duration型別的timeLimit引數來設定超時時間。如果原Future在超時之前完成,最終的結果就是該原Future的值;如果達到超時時間後還未完成,就會產生TimeoutException異常。 該方法有一個onTimeout可選引數,如果設定了該引數,當發生超時時會呼叫該函式,該函式的返回值為Future的新的值,而不會產生TimeoutException

其他構造方法

sync構造方法

factory Future.sync(FutureOr<T> computation())
複製程式碼

將會在當前task執行computation計算,而不是將計算過程新增到任務佇列中。

Future.micortask構造方法

factory Future.microtask(FutureOr<T> computation())
複製程式碼

通過scheduleMicrotask方法將computation函式新增到microtask佇列中,優先於event佇列執行。

factory Future.microtask(FutureOr<T> computation()) {
   _Future<T> result = new _Future<T>();
   scheduleMicrotask(() {
     try {
       result._complete(computation());
     } catch (e, s) {
       _completeWithErrorCallback(result, e, s);
     }
   });
   return result;
 }
複製程式碼

下面程式碼將會輸出
microtask future
default fauture

Future(() {
    print("default fauture");
});

Future.microtask(() {
  print("microtask future");
});
複製程式碼

value構造方法

factory Future.value([FutureOr<T> value]) {
    return new _Future<T>.immediate(value);
  }
複製程式碼

通過value值建立一個Future物件,vuale可以是一個確定的值或者是一個future物件。該方法使用了內部的immediate函式,這個函式會使用scheduleMicrotaskmicrotask佇列中更新Future的值。

error構造方法

factory Future.error(Object error, [StackTrace stackTrace])
複製程式碼

通過error物件和可選的stackTrace建立Future,可以使用該方法建立個一個狀態為failedFuture物件。

delayed構造方法

factory Future.delayed(Duration duration, [FutureOr<T> computation()]);
複製程式碼

建立一個延時執行的Future物件,時間由duration引數設定,到達指定事件後,將會執行computation函式。如果computation函式為空,Future的值為null

wait靜態方法

static Future<List<T>> wait<T>(Iterable<Future<T>> futures,{bool eagerError: false, void cleanUp(T successValue)})
複製程式碼

類似於JS中Promise的all方法。wait靜態方法可以等待多個Future執行完成,並通過List獲取所有Future的結果。如果其中一個Future物件發生異常,會導致最終結果為failed。 該方法有兩個可選引數,eagerErrorcleanUpeagerError預設值為false,當某一個Future發生異常時,預設不會立刻使Future處於failed狀態,而是等待所有Future都有結果後,改變狀態為failed。如果設定為true,當其中一個Future發生異常時,會立刻導致最終結果為failed, 如果設定了cleanUp引數,當多個Future中的一個發生異常時,其他成功的Future的(非null)結果會傳遞給cleanUp引數。如果沒有發生異常cleanUp函式不會被呼叫。

靜態方法

any靜態方法

static Future<T> any<T>(Iterable<Future<T>> futures)
複製程式碼

類似於JS中Promise的race方法。any靜態方法可以多個Future中第一個處於完成狀態的Future的結果,如果第一個完成的Future處於成功狀態,則新的Future狀態為成功。如果第一個完成的Future處於失敗狀態,則新的Future狀態為失敗。其他Future的狀態將被忽略。 wait any方法的引數都可以根據引數型別,推斷出新的Future型別。如果引數型別相同,則新的Future型別於引數型別一致,如果不同,則為Object型別。

forEach靜態方法

forEach方法有點像Stream的asycMap函式。

static Future forEach<T>(Iterable<T> elements, FutureOr action(T element))
複製程式碼

forEach靜態方法可以遍歷Iterable中的每個元素執行一個操作,如果遍歷操作返回的是Future物件,則在該Future完成後再進行下一次遍歷,全部完成後返回null。如果在某一次操作中發生異常,會停止遍歷,最終的Future的狀態為failed。

doWhile靜態方法

static Future doWhile(FutureOr<bool> action())
複製程式碼

doWhile靜態方法是 do while迴圈的非同步版本。可以一直執行一個action,直到action的值為false。 當迴圈結束時,doWhile方法返回的Future的值也會是null(為什麼要說也)。

總結

Future提供了豐富的Api來處理非同步任務,除了這些Api外,還有其他工具可以配合使用。比如可以使用FutureBuilder將Future的結果反應到UI介面上;可以使用asStream方法將Future轉換為Stream;還可以配合asyncawait來使用,以同步的方式處理非同步任務等等。

相關文章