Dart類庫有非常多的返回Future
或者Stream
對象的函數(shù)得糜。 這些函數(shù)被稱為異步函數(shù):它們只會在設(shè)置好一些耗時(shí)操作之后返回,比如像IO操作教寂。而不是等到這個(gè)操作完成籍滴。
async
和await
關(guān)鍵詞支持了異步編程,允許您寫出和同步代碼很像的異步代碼贰镣。
*注:Dart語言并不存在異步線程呜象,async
和await
只是處理成很像異步的操作
1. Future
Future
與JavaScript中的Promise
非常相似膳凝,表示一個(gè)異步操作的最終完成(或失敗)及其結(jié)果值的表示恭陡。簡單來說蹬音,它就是用于處理異步操作的,異步處理成功了就執(zhí)行成功的操作休玩,異步處理失敗了就捕獲錯(cuò)誤或者停止后續(xù)操作著淆。一個(gè)Future只會對應(yīng)一個(gè)結(jié)果,要么成功拴疤,要么失敗永部。
由于本身功能較多,這里我們只介紹其常用的API及特性呐矾。還有苔埋,請記住,Future
的所有API的返回值仍然是一個(gè)Future
對象蜒犯,所以可以很方便的進(jìn)行鏈?zhǔn)秸{(diào)用组橄。
(1)Future.then
為了方便示例,在本例中我們使用Future.delayed
創(chuàng)建了一個(gè)延時(shí)任務(wù)(實(shí)際場景會是一個(gè)真正的耗時(shí)任務(wù)罚随,比如一次網(wǎng)絡(luò)請求)玉工,即2秒后返回結(jié)果字符串"hi world!",然后我們在then
中接收異步結(jié)果并打印結(jié)果淘菩,代碼如下:
Future.delayed(Duration(seconds: 2),(){
return "hi world!";
}).then((data){
print(data);
});
(2)Future.catchError
如果異步任務(wù)發(fā)生錯(cuò)誤遵班,我們可以在catchError
中捕獲錯(cuò)誤,我們將上面示例改為:
Future.delayed(Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//執(zhí)行成功會走到這里
print("success");
}).catchError((e){
//執(zhí)行失敗會走到這里
print(e);
});
在本示例中瞄勾,我們在異步任務(wù)中拋出了一個(gè)異常费奸,then
的回調(diào)函數(shù)將不會被執(zhí)行,取而代之的是 catchError
回調(diào)函數(shù)將被調(diào)用进陡;但是,并不是只有 catchError
回調(diào)才能捕獲錯(cuò)誤微服,then
方法還有一個(gè)可選參數(shù)onError
趾疚,我們也可以用它來捕獲異常:
Future.delayed(Duration(seconds: 2), () {
//return "hi world!";
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
(3)Future.whenComplete
有些時(shí)候,我們會遇到無論異步任務(wù)執(zhí)行成功或失敗都需要做一些事的場景以蕴,比如在網(wǎng)絡(luò)請求前彈出加載對話框糙麦,在請求結(jié)束后關(guān)閉對話框。這種場景丛肮,有兩種方法赡磅,第一種是分別在then
或catch
中關(guān)閉一下對話框,第二種就是使用Future
的whenComplete
回調(diào)宝与,我們將上面示例改一下:
Future.delayed(Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//執(zhí)行成功會走到這里
print(data);
}).catchError((e){
//執(zhí)行失敗會走到這里
print(e);
}).whenComplete((){
//無論成功或失敗都會走到這里
});
(4)Future.wait
有些時(shí)候焚廊,我們需要等待多個(gè)異步任務(wù)都執(zhí)行結(jié)束后才進(jìn)行一些操作冶匹,比如我們有一個(gè)界面,需要先分別從兩個(gè)網(wǎng)絡(luò)接口獲取數(shù)據(jù)咆瘟,獲取成功后嚼隘,我們需要將兩個(gè)接口數(shù)據(jù)進(jìn)行特定的處理后再顯示到UI界面上,應(yīng)該怎么做袒餐?答案是Future.wait
飞蛹,它接受一個(gè)Future
數(shù)組參數(shù),只有數(shù)組中所有Future
都執(zhí)行成功后灸眼,才會觸發(fā)then
的成功回調(diào)卧檐,只要有一個(gè)Future
執(zhí)行失敗,就會觸發(fā)錯(cuò)誤回調(diào)焰宣。下面泄隔,我們通過模擬Future.delayed
來模擬兩個(gè)數(shù)據(jù)獲取的異步任務(wù),等兩個(gè)異步任務(wù)都執(zhí)行成功時(shí)宛徊,將兩個(gè)異步任務(wù)的結(jié)果拼接打印出來佛嬉,代碼如下:
Future.wait([
// 2秒后返回結(jié)果
Future.delayed(Duration(seconds: 2), () {
return "hello";
}),
// 4秒后返回結(jié)果
Future.delayed(Duration(seconds: 4), () {
return " world";
})
]).then((results){
print(results[0]+results[1]);
}).catchError((e){
print(e);
});
執(zhí)行上面代碼,4秒后你會在控制臺中看到“hello world”闸天。
2. async/await
Dart中的async/await
和JavaScript中的async/await
功能是一樣的:異步任務(wù)串行化暖呕。如果你已經(jīng)了解JavaScript中的async/await
的用法,可以直接跳過本節(jié)苞氮。
(1)回調(diào)地獄(Callback Hell)
如果代碼中有大量異步邏輯湾揽,并且出現(xiàn)大量異步任務(wù)依賴其他異步任務(wù)的結(jié)果時(shí),必然會出現(xiàn)Future.then
回調(diào)中套回調(diào)情況笼吟。舉個(gè)例子库物,比如現(xiàn)在有個(gè)需求場景是用戶先登錄,登錄成功后會獲得用戶ID贷帮,然后通過用戶ID戚揭,再去請求用戶個(gè)人信息,獲取到用戶個(gè)人信息后撵枢,為了使用方便民晒,我們需要將其緩存在本地文件系統(tǒng),代碼如下:
//先分別定義各個(gè)異步任務(wù)
Future<String> login(String userName, String pwd){
...
//用戶登錄
};
Future<String> getUserInfo(String id){
...
//獲取用戶信息
};
Future saveUserInfo(String userInfo){
...
// 保存用戶信息
};
接下來锄禽,執(zhí)行整個(gè)任務(wù)流:
login("alice","******").then((id){
//登錄成功后通過潜必,id獲取用戶信息
getUserInfo(id).then((userInfo){
//獲取用戶信息后保存
saveUserInfo(userInfo).then((){
//保存用戶信息,接下來執(zhí)行其他操作
...
});
});
})
可以感受一下沃但,如果業(yè)務(wù)邏輯中有大量異步依賴的情況磁滚,將會出現(xiàn)上面這種在回調(diào)里面套回調(diào)的情況,過多的嵌套會導(dǎo)致的代碼可讀性下降以及出錯(cuò)率提高宵晚,并且非常難維護(hù)垂攘,這個(gè)問題被形象的稱為回調(diào)地獄(Callback Hell)维雇。回調(diào)地獄問題在之前 JavaScript 中非常突出搜贤,也是 JavaScript 被吐槽最多的點(diǎn)谆沃,但隨著 ECMAScript 標(biāo)準(zhǔn)發(fā)布后,這個(gè)問題得到了非常好的解決仪芒,而解決回調(diào)地獄的兩大神器正是 ECMAScript6 引入了Promise
唁影,以及ECMAScript7 中引入的async/await
。 而在 Dart 中幾乎是完全平移了 JavaScript 中的這兩者:Future
相當(dāng)于Promise
掂名,而async/await
連名字都沒改据沈。接下來我們看看通過Future
和async/await
如何消除上面示例中的嵌套問題。
(2)消除回調(diào)地獄
消除回調(diào)地獄主要有兩種方式:
①饺蔑、使用Future消除Callback Hell
login("alice","******").then((id){
return getUserInfo(id);
}).then((userInfo){
return saveUserInfo(userInfo);
}).then((e){
//執(zhí)行接下來的操作
}).catchError((e){
//錯(cuò)誤處理
print(e);
});
正如上文所述锌介, Future
的所有API的返回值仍然是一個(gè)Future
對象,所以可以很方便的進(jìn)行鏈?zhǔn)秸{(diào)用”* 猾警,如果在then 中返回的是一個(gè)Future
的話孔祸,該future
會執(zhí)行,執(zhí)行結(jié)束后會觸發(fā)后面的then
回調(diào)发皿,這樣依次向下崔慧,就避免了層層嵌套。
②穴墅、使用 async/await 消除 callback hell
通過Future
回調(diào)中再返回Future
的方式雖然能避免層層嵌套惶室,但是還是有一層回調(diào),有一種方式能夠讓我們可以像寫同步代碼那樣來執(zhí)行異步任務(wù)而不使用回調(diào)的方式玄货,這就要使用async/await
了皇钞,下面我們先直接看代碼,然后再解釋松捉,代碼如下:
task() async {
try{
String id = await login("alice","******");
String userInfo = await getUserInfo(id);
await saveUserInfo(userInfo);
//執(zhí)行接下來的操作
} catch(e){
//錯(cuò)誤處理
print(e);
}
}
async
用來表示函數(shù)是異步的夹界,定義的函數(shù)會返回一個(gè)Future
對象,可以使用 then 方法添加回調(diào)函數(shù)惩坑。await
后面是一個(gè)Future
掉盅,表示等待該異步任務(wù)完成,異步完成后才會往下走以舒;await
必須出現(xiàn)在async
函數(shù)內(nèi)部。
可以看到慢哈,我們通過async/await
將一個(gè)異步流用同步的代碼表示出來了蔓钟。