本文章所寫的代碼github地址
Dart是一個在線程中運行的程序,這意味著:如果程序再執(zhí)行中遇到一個需要長時間執(zhí)行的操作,程序?qū)ㄗ?,這不就尷尬了嘛,為了避免這種尷尬的情況,可以使用異步操作使程序在等待一個耗時操作完成時繼續(xù)處理其他工作。在Dart中,可以使用Future對象來表示異步操作的結(jié)果。
Dart的消息循環(huán)機(jī)制
在進(jìn)入正題之前,我們先看一下Dart的消息循環(huán)機(jī)制:

簡單總結(jié)一下,詳細(xì)內(nèi)容可以看文章The Event Loop and Dart
Dart中事件循環(huán)的一些主要概念:
- Dart從兩個隊列執(zhí)行任務(wù):
event事件隊列和microtask微任務(wù)隊列,優(yōu)先處理microtask微任務(wù)隊列; - Dart的方法是不會被其他Dart代碼打斷的,當(dāng)main執(zhí)行完成后,
main isolate[1]的線程就會去逐一處理消息隊列中的消息。 - 事件隊列具有來自Dart(Future,Timer,isolate[1] Message等)和系統(tǒng)(用戶輸入,I/O等);
- 微任務(wù)隊列目前僅包含來自Dart(這句話我也不太懂);
- 事件循環(huán)會優(yōu)先處理微任務(wù)隊列,microtask清空之后才開始處理event事件隊列。
- 一旦兩個隊列都為空,則應(yīng)用程序已經(jīng)完成工作,并且(取決于其嵌入程序)可以退出。
- main() 函數(shù)以及微任務(wù)和事件隊列中的所有項目都在Dart應(yīng)用程序的main ioslate[1]上運行。
什么是Future
Future<T>(T表示泛型)表示一個指定類型的異步操作結(jié)果(不需要結(jié)果的情況可以使用Future<void>),當(dāng)一個返回Future對象的函數(shù)被調(diào)用時:
- 將函數(shù)放入隊列等待執(zhí)行并返回一個未完成的Future對象
- 當(dāng)函數(shù)執(zhí)行完成,F(xiàn)uture對象中會被賦值執(zhí)行的結(jié)果,已經(jīng)執(zhí)行的狀態(tài)
- 運行狀態(tài)(pending),白噢是任務(wù)還未完成,也沒有返回值
完成狀態(tài)(completed),表示任務(wù)已經(jīng)完成(無論失敗還是成功)
例如:
觀看程序輸出,首先執(zhí)行完main函數(shù),然后再去執(zhí)行任務(wù)棧中的內(nèi)容,在該例中也就是我們使用Future加入到event任務(wù)棧中,then中的方法會在Future處于完成態(tài)(completed)時立馬執(zhí)行,之后我們在詳細(xì)講解。
Dart提供了幾種Future的創(chuàng)建方法,其中常有的有:
factory Future(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
Timer.run(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
future.dart中所使用的就是這種方式創(chuàng)建的Future。
其他創(chuàng)建Future的方式包括:
- Future.value() :返回一個指定值的Future
Future.delayed() :返回一個延時執(zhí)行的Future
兩種方式的執(zhí)行效果
這端代碼執(zhí)行了兩個分支:
- main()方法
- event()隊列
Future中的任務(wù)調(diào)度
前面說過:當(dāng)Future執(zhí)行完成后,then()注冊的回調(diào)函數(shù)會立即執(zhí)行,但是then中的函數(shù)并不會被添加到事件隊列中,只是在事件隊列中的任務(wù)被執(zhí)行完成后才被立刻執(zhí)行(可以理解為:將網(wǎng)絡(luò)請求放在隊列中進(jìn)行執(zhí)行,拿到結(jié)果后在then中刷新UI)。

首先,人物隊列是以FIFO的方式進(jìn)行,f1,f2,f3依次被加入到任務(wù)棧,then()注冊的函數(shù)并不會被添加到隊列,也不會直接運行。當(dāng)任務(wù)棧中的人物被執(zhí)行后,立刻執(zhí)行then中的函數(shù),依次類推,可以看到,then中回調(diào)函數(shù)執(zhí)行順序并不取決于注冊的順序,而僅僅與其Future被加入到任務(wù)棧的順序有關(guān)。
注意:new Future(()=>null)和new Future(null)有本質(zhì)的區(qū)別,一個函數(shù)題為空,什么都不做,一個是參數(shù)為空,不存在函數(shù)。
稍微修改一下上例的代碼:

是否會對結(jié)果有所疑惑呢,先看一下
then的定義吧
Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError});
這里設(shè)計到兩個關(guān)健點:
- 如果Future在then被調(diào)用之前已經(jīng)完成,那么then中的函數(shù)會被作為任務(wù)添加到microtask隊列中;
- then會返回新的Future,并且該Future在
onValue(then中注冊的回調(diào)函數(shù))或者onError被執(zhí)行時就已經(jīng)處于完成狀態(tài)了。 -
如果onValue(回調(diào)函數(shù))返回值為一個Future,那么then返回的Future將會在onValue返回的future執(zhí)行完成后處于完成狀態(tài)
關(guān)于后面兩點:
其中,每個then都會返回一個新的Future,而該future會在onValue,也就是回調(diào)函數(shù)執(zhí)行時處于完成狀態(tài),然后立即執(zhí)行該future的回調(diào)函數(shù)。

注意,then方法本身會返回一個Future。在then中的函數(shù)也返回一個Future,而then所返回的Future會緊跟著函數(shù)返回的future之后處于完成狀態(tài)再執(zhí)行后續(xù)的回調(diào)函數(shù)。
總結(jié)一下:
- 當(dāng)Future任務(wù)完成后,then()注冊的回調(diào)函數(shù)會立即執(zhí)行。需注意的是,then()注冊的函數(shù)并不會添加到事件隊列中,回調(diào)函數(shù)只是在事件循環(huán)中任務(wù)完成后被調(diào)用。
- 如果Future在then()被調(diào)用之前已經(jīng)完成計算,那么任務(wù)會被添加到微任務(wù)隊列中,并且該任務(wù)會執(zhí)行then()中注冊的回調(diào)函數(shù)。
- then會返回新的Future,并且該Future在onValue(then中注冊的回調(diào)函數(shù))或者onError被執(zhí)行時就已經(jīng)處于完成狀態(tài)了。
- 如果onValue(回調(diào)函數(shù))返回值為一個Future,那么then返回的Future將會在onValue返回的future執(zhí)行完成后處于完成狀態(tài)
如果處理異步操作的結(jié)果
包括上面提到的then,有三種方法處理Future的結(jié)果:
- then:處理操作執(zhí)行結(jié)果或者錯誤并返回一個新的Future
- catchError: 注冊一個處理錯的回調(diào)
- whenComplete:類似final,無論錯誤還是正確,F(xiàn)uture執(zhí)行結(jié)束后總是被調(diào)用
then中的onError只能處理當(dāng)前Future中的錯誤,而catchError能處理整條調(diào)用鏈上的任何錯誤。

async和await
上面說了Future的基本用法,以及使用Future API處理數(shù)據(jù)的方法,但是這種方法存在一個問題:使用鏈?zhǔn)秸{(diào)用的方式把多個future連接在一起,會嚴(yán)重降低代碼的可讀性。
可以使用async和await關(guān)鍵字實現(xiàn)異步的功能。async和await可以幫助我們像寫同步代碼一樣編寫異步代碼

**注意:await只能在async函數(shù)里出現(xiàn) **
要想改變異步代碼,只需要在函數(shù)中添加async關(guān)鍵字
String getAString() {
return "我是一個字符串";
}
## 改寫為異步代碼
Future<String> getAString() async{
return "我是一個字符串";
}
需要注意的是,在普通函數(shù)中,return返回的為T,那么async函數(shù)中返回 的是Future<T>。但是并不需要顯示的去指定返回的類型,Dart會自動將返回值包裝成Future對象。但是,如果原函數(shù)返回的為Future<T>,在async函數(shù)中返回的仍然是Future<T>,若async函數(shù)沒有返回值,那么Dart會返回一個null值的Future。

注意觀察代碼的執(zhí)行順序,函數(shù)按照順序執(zhí)行,首先執(zhí)行test9函數(shù),接著按照順序執(zhí)行firstString()、secondString()、 thirdString().Future.delayed并不會阻礙任何代碼的執(zhí)行,這符合上文中講的非阻塞任何代碼的執(zhí)行,F(xiàn)uture并不會阻塞它所在函數(shù)的執(zhí)行。
我們稍微修改一下代碼:

對比兩次結(jié)果不難發(fā)現(xiàn),async和await關(guān)鍵字使得原本非阻塞式的函數(shù)變得同步了,成了阻塞函數(shù)了。函數(shù)遇到Future,在其未執(zhí)行完之前一直處于阻塞狀態(tài)。但是test10函數(shù)依舊正常執(zhí)行。并不會被async函數(shù)所阻塞。async和await只會作用當(dāng)前函數(shù),并不會對其他外部函數(shù)造成執(zhí)行上的影響。
await也可以幫助我們在執(zhí)行下個語句之前確保當(dāng)前語句執(zhí)行完畢:



