首先Dart
是一門單線程的語言,那麼Dart
對異步操作
對支持,可以使我們在編寫Dart
程序時可以異步的來執行耗時操作。從而可以在等待一個操作完成的同時進行別的操作以下是一些常見的異步操作:
-
通過網絡獲取數據。
-
寫入數據庫。
-
從文件讀取數據。
要在Dart
中執行異步操作,可以使用Future
類和async
和await
關鍵字。
# Dart的事件循環(event loop)
在Dart
中,實際上有兩種隊列:
-
事件隊列(
event queue
),包含所有的外來事件:I/O
、mouse events
、drawing events
、timers
、isolate
之間的信息傳遞。 -
微任務隊列(
microtask queue
),表示一個短時間內就會完成的異步任務。它的優先級最高,高於event queue
,只要隊列中還有任務,就可以一直霸佔着事件循環。microtask queue
添加的任務主要是由Dart
內部產生。
因爲
microtask queue
的優先級高於event queue
,所以如果microtask queue
有太多的微任務, 那麼就可能會霸佔住當前的event loop
。從而對event queue
中的觸摸、繪製等外部事件造成阻塞卡頓。
在每一次事件循環中,Dart
總是先去第一個microtask queue
中查詢是否有可執行的任務,如果沒有,纔會處理後續的event queue
的流程。
異步任務我們用的最多的還是優先級更低的 event queue
。Dart
爲 event queue
的任務建立提供了一層封裝,就是我們在Dart
中經常用到的Future
。
正常情況下,一個 Future
異步任務的執行是相對簡單的:
-
聲明一個
Future
時,Dart
會將異步任務的函數執行體放入event queue
,然後立即返回,後續的代碼繼續同步執行。 -
當同步執行的代碼執行完畢後,
event queue
會按照加入event queue
的順序(即聲明順序),依次取出事件,最後同步執行Future
的函數體及後續的操作。
# Future
Future<T>
類,其表示一個 T
類型的異步操作結果。如果異步操作不需要結果,則類型爲 Future<void>
。也就是說首先Future
是個泛型類,可以指定類型。如果沒有指定相應類型的話,則Future
會在執行動態的推導類型。
# Future基本用法
# Future工廠構造函數
什麼是工廠構造函數?
工廠構造函數是一種構造函數,與普通構造函數不同,工廠函數不會自動生成實例,而是通過代碼來決定返回的實例對象。
在Dart
中,工廠構造函數的關鍵字爲factory
。我們知道,構造函數包含類名構造函數和命名構造方法,在構造方法前加上factory
之後變成了工廠構造函數。也就是說factory
可以放在類名函數之前,也可以放在命名函數之前。
下面我們通過Future
的工廠構造函數,創建一個最簡單的Future
。
可以看到,Future
的工廠構造函數接收一個Dart
函數作爲參數。這個函數沒有參數,返回值是FutureOr<T>
類型。
從打印結果可以看出,Future
不需要結果時,返回的類型是 Future<void>
。
注意,是先執行的類型判斷,後打印的Future
內的操作。
# async
和await
默認的Future
是異步運行的。如果想要我們的Future
同步執行,可以通過async
和await
關鍵字:
可以看到,我們的Future
已經同步執行了。await
會等待Future
執行結束後,纔會繼續執行後面的代碼。
關鍵字async
和await
是Dart
語言異步支持的一部分。
異步函數即在函數頭中包含關鍵字
async
的函數。
-
async:用來表示函數是異步的,定義的函數會返回一個
Future
對象。 -
await:後面跟着一個
Future
,表示等待該異步任務完成,異步任務完成後纔會繼續往下執行。await
只能出現在異步函數內部。能夠讓我們可以像寫同步代碼那樣來執行異步任務而不使用回調的方式。
注意:在
Dart
中,async/await
都只是一個語法糖,編譯器或解釋器最終都會將其轉化爲一個Promise(Future)
的調用鏈。
在Dart 2.0之前,async函數會立即返回,而無需在async函數體內執行任何代碼
所以,如果我們將代碼改成下面的這種形式:
可以看到,我們的Future
已經同步執行了。await
會等待Future
執行結束後,纔會繼續執行後面的代碼。
關鍵字async
和await
是Dart
語言異步支持的一部分。
異步函數即在函數頭中包含關鍵字
async
的函數。
-
async:用來表示函數是異步的,定義的函數會返回一個
Future
對象。 -
await:後面跟着一個
Future
,表示等待該異步任務完成,異步任務完成後纔會繼續往下執行。await
只能出現在異步函數內部。能夠讓我們可以像寫同步代碼那樣來執行異步任務而不使用回調的方式。
注意:在
Dart
中,async/await
都只是一個語法糖,編譯器或解釋器最終都會將其轉化爲一個Promise(Future)
的調用鏈。
在Dart 2.0之前,async函數會立即返回,而無需在async函數體內執行任何代碼
所以,如果我們將代碼改成下面的這種形式:
當我們使用了async
關鍵字,意味着testFuture
函數已經變成了異步函數。
所以會先執行testFuture
函數之後的打印。
在執行完打印後,會開始檢查
microtask queue
中是否有任務,若有則執行,直到microtask queue
列隊爲空。因爲microtask queue
的優先級是最高的。然後再去執行event queue
。一般Future
創建的事件會插入event queue
順序執行(使用Future.microtask
方法例外)。
# Future.value()
創建一個返回指定value
值的Future
:
void testFuture() async {
var future = await Future.value(1);
print("future value: $future.");
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
future value: 1.
# Future.delayed()
創建一個延遲執行的Future
:
void testFuture() async {
Future.delayed(Duration(seconds: 2), () {
print("Future.delayed 2 seconds.");
});
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
Future.delayed 2 seconds.
# Future
簡單使用
# 處理Future
的結果
對於Future
來說,異步處理成功了就執行成功的操作,異步處理失敗了就捕獲錯誤或者停止後續操作。一個Future
只會對應一個結果,要麼成功,要麼失敗。
請記住,Future
的所有API
的返回值仍然是一個Future
對象,所以可以很方便的進行鏈式調用。
Dart
提供了下面三個方法用來處理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
完成時要調用的回調。如果 Future
有多個then
,它們也會按照鏈接的先後順序同步執行,同時也會共用一個event loop
。
void testFuture() async {
Future.value(1).then((value) {
return Future.value(value + 2);
}).then((value) {
return Future.value(value + 3);
}).then(print);
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
6
同時,then()
會在 Future
函數體執行完畢後立刻執行:
void testFuture() async {
var future = new Future.value('a').then((v1) {
return new Future.value('$v1 b').then((v2) {
return new Future.value('$v2 c').then((v3) {
return new Future.value('$v3 d');
});
});
});
future.then(print, onError: print);
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
a b c d
那麼問題來了,如果Future
已經執行完畢了,我們再來獲取到這個Future
的引用,然後再繼續調用then()
方法。那麼此時,Dart
會如何處理這種情況?對於這種情況,Dart
會將後續加入的then()
方法體放入microtask queue
,儘快執行:
-
因爲先調用的
testFuture()
函數,所以先打印future 13
。 -
再執行
testFuture()
後面的打印。 -
開始異步任務執行。
-
首先執行優先級最高的
microtask queue
任務scheduleMicrotask
,打印future 12
。 -
然後按照
Future
的聲明順序再執行,打印future 1
. -
然後到了
futureFinish
,打印future 2
。此時futureFinish
已經執行完畢。所以Dart
會將後續通過futureFinish
調用的then
方法放入microtask queue
。由於microtask queue
的優先級最高。因此futureFinish
的then
會最先執行,打印future 11
。 -
然後繼續執行
event queue
裏面的future 3
。然後執行then
,打印future 4
。同時在then
方法裏向microtask queue
裏添加了一個微任務。因爲此時正在執行的是event queue
,所以要等到下一個事件循環才能執行。因此後續的then
繼續同步執行,打印future 6
。本次事件循環結束,下一個事件循環取出future 5
這個微任務,打印future 5
。 -
microtask queue
任務執行完畢。繼續執行event queue
裏的任務。打印future 7
。然後執行then
。這裏需要注意的是:此時的then
返回的是一個新創建的Future
。因此這個then
,以及後續的then
都將被被添加到event queue
中了。 -
按照順序繼續執行
evnet queue
裏面的任務,打印future 10
。 -
最後一個事件循環,取出
evnet queue
裏面通過future 7
的then
方法新加入的future 8
,以及後續的future 9
,打印。 -
整個過程結束。
# Future.catchError
註冊一個回調,來捕捉Future
的error
:
void testFuture() async {
new Future.error('Future 發生錯誤啦!').catchError(print, test: (error) {
return error is String;
});
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
Future 發生錯誤啦!
# then
中的回調onError
和Future.catchError
Future.catchError
回調只處理原始Future
拋出的錯誤,不能處理回調函數拋出的錯誤,onError只能處理當前Future的錯誤:
void testFuture() async {
new Future.error('Future 發生錯誤啦!').then((_) {
throw 'new error';
}).catchError((error) {
print('error: $error');
throw 'new error2';
}).then(print, onError:(error) {
print("handle new error: $error");
});
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
error: Future 發生錯誤啦!
handle new error: new error2
# Future.whenComplete
Future.whenComplete
在Future完成之後總是會調用,不管是錯誤導致的完成還是正常執行完畢。比如在網絡請求前彈出加載對話框,在請求結束後關閉對話框。並且返回一個Future對象:
void testFuture() async {
var random = new Random();
new Future.delayed(new Duration(seconds: 1), () {
if (random.nextBool()) {
return 'Future 正常';
} else {
throw 'Future 發生錯誤啦!';
}
}).then(print).catchError(print).whenComplete(() {
print('Future whenComplete!');
});
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
Future 發生錯誤啦!
Future whenComplete!
在testFuture()執行之後打印。
Future 正常
Future whenComplete!
# Future高級用法
# Future.timeout
本來Future
會在2s後完成,但是timeout
聲明的是1s後超時,所以1s後Future
會拋出TimeoutException
:
void testFuture() async {
new Future.delayed(new Duration(seconds: 2), () {
return 1;
}).timeout(new Duration(seconds:1)).then(print).catchError(print);
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
TimeoutException after 0:00:01.000000: Future not completed
# Future.foreach
根據某個集合對象,創建一系列的Future
。並且會按順序執行這些Future
。例如,根據{1,2,3}創建3個延遲對應秒數的Future
。執行結果爲1秒後打印1,再過2秒打印2,再過3秒打印3,總時間爲6秒:
void testFuture() async {
Future.forEach({1,2,3}, (num){
return Future.delayed(Duration(seconds: num),(){print("第$num秒執行");});
});
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
第1秒執行
第2秒執行
第3秒執行
# Future.wait
等待多個Future
完成,並收集它們的結果。有兩種情況:
-
所有
Future
都有正常結果返回。則Future
的返回結果是所有指定Future
的結果的集合:void testFuture() async { 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); } testFuture(); print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。 [1, 2, 3]
-
其中一個或者幾個
Future
發生錯誤,產生了error
。則Future
的返回結果是第一個發生錯誤的Future
的值:void testFuture() async { var future1 = new Future.delayed(new Duration(seconds: 1), () => 1); var future2 = new Future.delayed(new Duration(seconds: 2), () { throw 'Future 發生錯誤啦!'; }); var future3 = new Future.delayed(new Duration(seconds: 3), () => 3); Future.wait({future1,future2,future3}).then(print).catchError(print); } testFuture(); print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。 Future 發生錯誤啦!
# Future.any
返回的是第一個執行完成的Future
的結果,不會管這個結果是正確的還是error
:
void testFuture() async {
Future
.any([1, 2, 5].map((delay) => new Future.delayed(new Duration(seconds: delay), () => delay)))
.then(print)
.catchError(print);
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
1
# Future.doWhile
重複性地執行某一個動作,直到返回false或者Future,退出循環,適用於一些需要遞歸操作的場景:
void testFuture() async {
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);
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
waited 5 seconds
waited 5 seconds
waited 3 seconds
total delay: 11 seconds
null
waited 4 seconds
waited 3 seconds
total delay: 12 seconds
null
# Future.sync
會同步執行其入參函數,然後調度到microtask queue
來完成自己。也就是一個阻塞任務,會阻塞當前代碼,sync
的任務執行完了,代碼才能走到下一行:
void testFuture() async {
Future((){
print("Future event 1");
});
Future.sync(() {
print("Future sync microtask event 2");
});
Future((){
print("Future event 3");
});
Future.microtask((){
print("microtask event");
});
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
Future sync microtask event 2
在testFuture()執行之後打印。
microtask event
Future event 1
Future event 3
但是注意,如果這個入參函數返回一個Future
:
void testFuture() async {
Future((){
print("Future event 1");
});
Future.sync(() {
return Future(() { print("Future event 2");
});
});
Future((){
print("Future event 3");
});
Future.microtask((){
print("microtask event");
});
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
microtask event
Future event 1
Future event 2
Future event 3
# Future.microtask
創建一個在microtask queue
運行的Future
。我們知道microtask queue
的優先級是比event queue
高的。而一般Future
是在event queue
執行的。所以Future.microtask
創建的Future
會優先於其他Future
執行:
void testFuture() async {
Future((){
print("Future event 1");
});
Future((){
print("Future event 2");
});
Future.microtask((){
print("microtask event");
});
}
testFuture();
print("在testFuture()執行之後打印。");
執行結果:
在testFuture()執行之後打印。
microtask event
Future event 1
Future event 2
# 寫在最後
通過這篇文章我們瞭解了Dart
中的事件循環和event queue
和microtask queue
之間關係。同時,介紹了一些關於Dart Future
的一些基礎使用和高級用法,同時穿插了一些使用實例,用來幫助大家更好的來理解Dart
中的異步操作。當然,還有一些關於Dart
異步編程和多線程的一些知識,這裏沒有過多的涉及。會在後續的文章來繼續給大家來講解。
# References
-
Asynchronous programming: futures, async, await
-
源碼地址