Dart 是單線程語言,開發種進行耗時操作(如:網絡請求、數據庫存儲、文件讀取)時會阻塞程序??梢杂?Future、async、await 把耗時的事件異步執行操作,但如果是執行大量耗時同步操作的話,應該使用isolate開辟新的線程去執行。
Future
Future<T> 類,是個泛型類,可以指定類型。如果沒有指定,則 Future 會執行動態的推導類型。表示一個 T 類型的異步操作結果。如果異步操作不需要結果,則類型為 Future<void>。
Future.value()
創建一個返回指定value值的Future
import 'dart:async'
void main() {
futureValueTest();
print('4-做其他事情');
}
void futureValueTest() async {
var future = await Future.value(1);
print(future);
}
運行結果:
flutter: 4-做其他事情
flutter: 1
Future.delay()
創建一個延遲執行的Future,并且返回Future對象。
void main() {
futterDelayTest();
print('4-做其他事情');
}
void futterDelayTest() {
Future.delayed(Duration(seconds: 3), () {
print("延時3秒執行");
});
}
運行結果:
flutter: 4-做其他事情
flutter: 延時3秒執行
Future 中實現的延時操作通過Timer來實現的,在實際開發中,如果只是一個單純的延時操作,建議使用Timer
void main() {
timerTest();
print('4-做其他事情');
}
void timerTest() {
Timer timer = new Timer(Duration(seconds: 3), () {
print("延時3秒執行");
});
}
運行結果:
flutter: 4-做其他事情
flutter: 延時3秒執行
Future的結果處理
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 只會對應一個結果,要么成功,要么失敗。成功了執行成功的操作,失敗了就捕獲錯誤。
Future.then()
用來注冊一個 Future 完成時要調用的回調,并且返回一個Future對象。
void main() {
Future(() => print('A')).then((value) => print('A結束'));
Future(() => print('B')).then((value) => print('B結束'));
}
運行結果:
flutter: A
flutter: A結束
flutter: B
flutter: B結束
- 如果 Future 有多個 then(),它們會按照鏈接的先后順序同步執行,并共用一個 event loop;
- then() 比 Future 默認的隊列優先級高,then() 會在 Future 函數體執行完畢后立刻執行。
Future.catchError()
用來注冊一個捕捉 Future 的錯誤的回調,并且返回一個 Future 對象
- then() 在 catchError() 前使用
String _data = '0';
void main() {
getData3();
print('4-做其他事情');
}
void getData3() async {
print('1-開始data=$_data');
Future(() {
for (int i = 0; i < 100000000; i++) {
_data = '網絡數據';
throw Exception('網絡異常');
}
print('1-結束data=$_data');
}).then((value) {
print('處理業務');
}).catchError((e) {
print('捕獲異常');
});
print('2-結束data=$_data');
}
運行結果:
flutter: 1-開始data=0
flutter: 2-結束data=0
flutter: 4-做其他事情
flutter: 捕獲異常
- then() 在 catchError() 后使用
String _data = '0';
void main() {
getData3();
print('4-做其他事情');
}
void getData4() async {
print('1-開始data=$_data');
Future(() {
for (int i = 0; i < 100000000; i++) {
_data = '網絡數據';
throw Exception('網絡異常');
}
print('1-結束data=$_data');
}).catchError((e) {
print('捕獲異常');
}).then((value) {
print('處理業務');
});
print('2-結束data=$_data');
}
運行結果:
flutter: 1-開始data=0
flutter: 2-結束data=0
flutter: 4-做其他事情
flutter: 捕獲異常
flutter: 處理業務
區別就是:then() 在 catchError() 后使時用,會走處理業務的回調
Future.catchError 和 then() 中回調 onError
Future.catchError回調只處理原始 Future 拋出的錯誤,不能處理回調函數拋出的錯誤,此時可以使用then()的回調onError:
void main() {
getData5();
print('4-做其他事情');
}
void getData5() async {
Future(() {
throw Exception('error1');
}).catchError((e) {
print(e);
throw Exception('error2');
}).then(print, onError: (error) {
print(error);
});
}
運行結果:
flutter: 4-做其他事情
flutter: Exception: error1
flutter: Exception: error2
Future.whenComplete()
不管是錯誤導致還是正常執行完畢, whenComplete() 總會被調用,并且返回一個 Future 對象
void main() {
Future(() {
throw '發生錯誤';
}).then(print).catchError(print).whenComplete(() => print('whenComplete'));
Future(() {
return '沒有錯誤';
}).then(print).catchError(print).whenComplete(() => print('whenComplete'));
}
運行結果:
flutter: 發生錯誤
flutter: whenComplete
flutter: 沒有錯誤
flutter: whenComplete
Future.wait()
開發中會遇到這樣的場景:網絡請求 A 和 B 都完成后,再執行代碼C,此時可以使用Future.wait() 。Future.wait() 會等待多個 Future 完成收集它們的結果。
- 所有 Future 都有正常結果返回
void main() {
futureWaitTest();
print('4-做其他事情');
}
void futureWaitTest() {
var future1 = new Future(() => '任務1');
var future2 = new Future(() => '任務2');
var future3 = new Future(() => '任務3');
Future.wait([future1, future2, future3]).then(print).catchError(print);
print('任務添加完畢');
}
運行結果:
flutter: 任務添加完畢
flutter: 4-做其他事情
flutter: [任務1, 任務2, 任務3]
- 一個或者幾個 Future 發生錯誤,則 Future 的返回結果是第一個發生錯誤的 Future 的值:
void main() {
futureWaitTest();
print('4-做其他事情');
}
void futureWaitTest() {
var future1 = new Future(() => '任務1');
var future2 = new Future(() => throw throw Exception('任務2異常'));
var future3 = new Future(() => '任務3');
Future.wait([future1, future2, future3]).then(print).catchError(print);
print('任務添加完畢');
}
運行結果:
flutter: 任務添加完畢
flutter: 4-做其他事情
flutter: Exception: 任務2異常
Future.timeout()
開發中會遇到這樣的場景:網絡請求 A 超過 30 秒后拋出超時異常
void main() {
futureTimeoutTest();
print('4-做其他事情');
}
void futureTimeoutTest() {
Future.delayed(Duration(seconds: 50), () {
return '網絡請求50秒';
}).timeout(Duration(seconds: 30)).then(print).catchError(print);
}
運行結果:
flutter: 4-做其他事情
flutter: TimeoutException after 0:00:03.000000: Future not completed
async、await
async-await 本質上是對 Future API 的簡化形式,將異步回調代碼寫成同步代碼結構形式
- async:表示函數是異步的,定義的函數會返回一個 Future 對象
- await:await 只能出現在 async 標記的異步函數內,后面跟著一個 Future,表示等待該異步任務完成后才會繼續往下執行。
async和await只會作用于當前函數,并不會對其他外部函數造成執行上的影響
main() {
print("main函數開始了");
firstString();
secondString();
thirdString();
print("main函數結束了");
}
firstString() async {
print("firstString函數開始了");
Future future = Future.delayed(Duration(milliseconds: 300), () {
return "我是一個字符串";
});
print(await future);
print("firstString函數結束了");
}
secondString() {
print("我是二個字符串");
}
thirdString() {
print("我是三個字符串");
}
輸出結果:
main函數開始了
firstString函數開始了
我是二個字符串
我是三個字符串
main函數結束了
我是一個字符串
firstString函數結束了
main() async {
print("main函數開始了:${DateTime.now()}");
print(await firstString());
print(await secondString());
print(await thirdString());
print("main函數結束了:${DateTime.now()}");
}
firstString() {
return Future.delayed(Duration(milliseconds: 300), () {
return "我是一個字符串";
});
}
secondString() {
return Future.delayed(Duration(milliseconds: 200), () {
return "我是二個字符串";
});
}
thirdString() {
return Future.delayed(Duration(milliseconds: 100), () {
return "我是三個字符串";
});
}
輸出結果:
main函數開始了:2020-10-13 16:24:46.897353
我是一個字符串
我是二個字符串
我是三個字符串
main函數結束了:2020-10-13 16:24:47.527151
異常下處理
void main() async {
//注意:需要添加async,因為await必須在async方法內才有效
try {
var userInfo = await _loadUserFromSQL();
var token = await _fetchSessionToken(userInfo);
var data = await _fetchData(token);
print('$data');
} on Exception catch (e) {
print('this is error: $e');
} finally {
print('all is done');
}
print('main is executed!');
}
通過對比發現使用 Future.then 相比 async-await 調用鏈路更清晰,基于異步回調的方式調用相比比較清晰。
而在代碼實現以及同步結構分析 async-await 顯得更加簡單且處理異常也更加方面,更加符合同步代碼的邏輯。
事件循環 - event loop
Flutter 中有兩種隊列
- 微任務隊列(microtask queue):主要是由 Dart 內部產生,表示一個短時間內就會完成的異步任務。(Future 的 then() 中的事件)【
優先級最高
】 - 事件隊列(event queue):包含所有的外部事件:I/O、mouse events、drawing events、timers、isolate之間的信息傳遞。(Future 的函數體)
- 微任務隊列會優先執行,只要隊列中有任務,就會一直占著事件循環。因此,如果有太多的微任務隊列存在,可能會對事件隊列中的觸摸、繪制等外部事件造成一定的阻塞
- Dart 會將 Future 異步任務的函數執行體放入 event queue,然后立即返回,后續 then() 的代碼繼續同步執行;當同步執行的代碼執行完畢后,event queue 會按照加入event queue的順序(即聲明順序),依次取出事件。
void main() {
futureQueueTest();
}
void futureQueueTest() {
Future future1 = Future(() => null);
future1.then((value) {
print('6');
scheduleMicrotask(() => print(7));
}).then((value) => print('8'));
Future future2 = Future(() => print('1'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
future2.then((value) => print('4'));
print('5');
}
運行結果
flutter: 5
flutter: 3
flutter: 6
flutter: 8
flutter: 7
flutter: 1
flutter: 4
flutter: 2
- 聲明 Future 時,先將它們的異步函數執行體放入 event queue ,然后立即返回,后續代碼繼續同步執行。所以先打印5;
- event loop循環 先選擇隊列優先級高的微任務3執行,打印3;
- event queue 按照加入 event queue的順序(即聲明順序),依次取出事件,最后同步執行future1、future2的函數體及后續的操作。所以再打印6, 并將新聲明的微任務7加入 event loop循環 ;
- future1的函數體結束后,優先執行 future1 的后續操作then()。所以再打印8;
- 此時 future1 函數體及后續的操作全部執行完畢,再次 event loop循環選擇隊列優先級高的 微任務7 執行,打印 7 ;
- 再次 event loop循環 ,執行 future2 的函數體及后續的操作,打印1和4;
- 再次 event loop循環 ,執行future的函數體及后續的操作,打印2;
參考資料:
https://juejin.cn/post/6976136324499636261#heading-15
http://www.lxweimin.com/p/07047a534a01
https://juejin.cn/post/6939510009654935588