餓補一下Flutter中Http請求的異步操作。
Dart是一個單線程語言,可以理解成物理線路中的串聯,當其遇到有延遲的運算(比如IO操作、延時執行)時,線程中按順序執行的運算就會阻塞,用戶就會感覺到卡頓,於是通常用異步處理來解決這個問題。
Dart異步編程有兩種方式:Future和Stream
Future相當於40米大砍刀,Stream相當於一捆40米大砍刀。dart提供了關鍵字async(異步)和await(延遲執行),相當於普通的便捷的小匕首,而小匕首是我們平時經常用到的。
當遇到有需要延遲的運算(async)時,將其放入到延遲運算的隊列(await)中去,把不需要延遲運算的部分先執行掉,最后再來處理延遲運算的部分。
1、async和await
async await 這兩個關鍵字是dart語言的特性,能讓你寫出看起來像是“同步”的“異步”代碼,先看一個方法案例:
/*HTTP的get請求返回值為Future<String>類型,即其返回值未來是一個String類型的值*/
/*async關鍵字聲明該函數內部有代碼需要延遲執行*/
getData() async {
/*await關鍵字聲明運算為延遲執行,然后return運算結果*/
return await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
}
然后我們嘗試調用這個方法,並獲取返回值。
String data = getDate();
然后控制台報錯了….
為什么呢?因為data是String類型,而函數getData()是一個異步操作函數,其返回值是一個await延遲執行的結果。在Dart中,有await標記的運算,其結果值都是一個Future對象,Future不是String類型,所以就報錯了。
總結一下:
在請求方法中直接 return await .. .的時候,實際上返回的是一個延遲計算的Future對象。
還有兩點需要注意:
- await關鍵字必須在async函數內部使用
- 調用async函數必須使用await關鍵字
后面兩點怎么講?
- 要想 return await … ,那么方法首先是 async 的,如上方方法。
- 使用 async 標注的方法,必須要用 await 接收返回值,比如上方方法在接收返回值時,需要加入 await ,var data = await getData();
2、什么是Future
Future表示一件“將來”會發生的事情,將來可以從Future中取到一個值。當一個方法返回一個Future的事情,發生兩件事情:
- 這個方法將某件事情排隊,返回一個未完成的Future
- 當這件事情完畢之后,Future的狀態會變成已完成,這個時候就可以取到這件事情的返回值了。
要取到這個“返回值”,有兩種方式:
- 使用async配合await
- 使用Future提供的api
我們看這兩種實現方式的案例:
2.1、使用async配合await
先看個案例,等待3秒后返回‘我是用戶’:
/*模擬異步加載用戶信息*/
Future _getUserInfo() async{
await new Future.delayed(new Duration(milliseconds: 3000));
return "我是用戶";
}
/*加載用戶信息,順便打印時間看看順序*/
Future _loadUserInfo() async{
print("_loadUserInfo:${new DateTime.now()}");
print(await _getUserInfo());
print("_loadUserInfo:${new DateTime.now()}");
}
我們在initState中調用該方法:
@override
void initState(){
print("initState:${new DateTime.now()}");
_loadUserInfo();
print("initState:${new DateTime.now()}");
super.initState();
}
打印結果如下:
I/flutter ( 1802): initState:2019-06-20 09:46:40.097339
I/flutter ( 1802): _loadUserInfo:2019-06-20 09:46:40.103542
I/flutter ( 1802): Instance of 'Future<dynamic>'
I/flutter ( 1802): initState:2019-06-20 09:46:40.108510
I/flutter ( 1802): 我是用戶
I/flutter ( 1802): _loadUserInfo:2019-06-20 09:46:43.117136
what?
很明顯,打印結果並沒有按照串聯的方式依次打印。
flutter中會改造帶asyc關鍵字的方法,讓這個方法脫離主流程,變成“后面一點”執行(通過scheduleMicrotask),所以可以讓我們的程序“看起來”是順序執行的。
2.2、Future api
我們修改一下 loadUserInfo() 方法:
/*加載用戶信息,順便打印時間看看順序*/
Future _loadUserInfo() async{
print("_loadUserInfo:${new DateTime.now()}");
_getUserInfo().then((info){
print(info);
});
print("_loadUserInfo:${new DateTime.now()}");
}
再次運行輸出一下:
I/flutter ( 1802): initState:2019-06-20 09:50:32.488765
I/flutter ( 1802): _loadUserInfo:2019-06-20 09:50:32.494751
I/flutter ( 1802): _loadUserInfo:2019-06-20 09:50:32.499725
I/flutter ( 1802): Instance of 'Future<dynamic>'
I/flutter ( 1802): initState:2019-06-20 09:50:32.499970
I/flutter ( 1802): 我是用戶
兩次輸出是有不同的,主要不同在於第二個 loadUserInfo 的日志打印,與‘我是用戶’的輸出順序,為什么有差異?
await會阻塞流程,等待緊跟着的的Future執行完畢之后,再執行下一條語句,而如果用了Future.then這個api,那么就不會等待,直接執行下面的語句,等Future執行完了,再調用then這個方法。
3、總結
在請求方法中直接 return await .. .的時候,實際上返回的是一個延遲計算的Future對象。
有兩點需要注意:
- await關鍵字必須在async函數內部使用
- 調用async函數必須使用await關鍵字
Flutter 中有兩種實現異步編程的方式:Future api、 async await
日常開發中常用的是 async await Future 搭配。