在客戶端的很多操作需要非同步執行,使用callback處理非同步,很容易寫出回撥地獄(Callback hell)程式碼,導致難以維護。所以在Web端出現了Promise
,Android有RxJava
,可以使用這些工具,將橫向巢狀的callback程式碼改為縱向擴充套件,更符合人類的直覺,也更加容易維護。配合async,await更是能以同步的方式寫非同步程式碼。
Dart也同樣提供了處理非同步的工具Future
,和JavaScript中的Promise
十分相似,有Promise
使用經驗的人使用Future
時會感到十分熟悉。
An object representing a delayed computation.
Future
是一個表示延遲計算的物件。代表一些計算將非同步進行,並在將來獲取到計算的結果。
Future有兩種狀態:Pending
和 Complete
。Pending
表示任務正在執行中,還未獲取到結果;Complete
表示任務已經執行完,並且有結果。
結果有兩種值:成功(succeed)或者失敗(failed), 我們可以使用回撥函式來獲取結果值。
事件迴圈
和其他客戶端開發框架類似(JS中的EventLoop, Android中的Looper,iOS中的RunLoop),Dart中也有一個無線迴圈的事件迴圈,包含兩個佇列: microtask佇列
和event佇列
。Dart會不斷地迴圈訪問這兩個佇列,從中取出任務來執行。microtask佇列
的優先順序高於event佇列
,如果microtask佇列
不為空,就從中取出任務執行。當microtask佇列
為空時,才會訪問event佇列
。
通過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>
型別。
通過這個方法建立的Future
,computation
函式會被新增到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
函式後,可以再呼叫then
、catchError
等方法,但是後面註冊回撥函式並不能獲取到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
函式,這個函式會使用scheduleMicrotask
在microtask佇列
中更新Future的值。
error構造方法
factory Future.error(Object error, [StackTrace stackTrace])
複製程式碼
通過error
物件和可選的stackTrace
建立Future
,可以使用該方法建立個一個狀態為failed
的Future
物件。
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。
該方法有兩個可選引數,eagerError
和cleanUp
。
eagerError
預設值為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
;還可以配合async
和await
來使用,以同步的方式處理非同步任務等等。