Dart類庫(kù)有非常多的返回Future或者Stream對(duì)象的函數(shù)。 這些函數(shù)被稱為異步函數(shù):它們只會(huì)在設(shè)置好一些耗時(shí)操作之后返回刻获,比如像 IO操作坪创。而不是等到這個(gè)操作完成。
async和await關(guān)鍵詞支持了異步編程侍瑟,運(yùn)行您寫出和同步代碼很像的異步代碼唐片。
Future
Future與JavaScript中的Promise非常相似,表示一個(gè)異步操作的最終完成(或失斦茄铡)及其結(jié)果值的表示费韭。簡(jiǎn)單來說,它就是用于處理異步操作的庭瑰,異步處理成功了就執(zhí)行成功的操作星持,異步處理失敗了就捕獲錯(cuò)誤或者停止后續(xù)操作。一個(gè)Future只會(huì)對(duì)應(yīng)一個(gè)結(jié)果弹灭,要么成功督暂,要么失敗。
由于本身功能較多穷吮,這里我們只介紹其常用的API及特性逻翁。還有,請(qǐng)記住捡鱼,F(xiàn)uture 的所有API的返回值仍然是一個(gè)Future對(duì)象八回,所以可以很方便的進(jìn)行鏈?zhǔn)秸{(diào)用。
Future.then
Future.delayed 創(chuàng)建了一個(gè)延時(shí)任務(wù)(實(shí)際場(chǎng)景會(huì)是一個(gè)真正的耗時(shí)任務(wù),比如一次網(wǎng)絡(luò)請(qǐng)求)缠诅,即2秒后返回結(jié)果字符串"hi world!"伟墙,然后我們?cè)趖hen中接收異步結(jié)果并打印結(jié)果,代碼如下:
Future.delayed(new Duration(seconds: 2),(){
return "hi world!";
}).then((data){
print(data);
});
Future.catchError
如果異步任務(wù)發(fā)生錯(cuò)誤滴铅,我們可以在catchError中捕獲錯(cuò)誤戳葵,我們將上面示例改為:
Future.delayed(new Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//執(zhí)行成功會(huì)走到這里
print("success");
}).catchError((e){
//執(zhí)行失敗會(huì)走到這里
print(e);
});
在本示例中,我們?cè)诋惒饺蝿?wù)中拋出了一個(gè)異常汉匙,then的回調(diào)函數(shù)將不會(huì)被執(zhí)行拱烁,取而代之的是 catchError回調(diào)函數(shù)將被調(diào)用;但是噩翠,并不是只有 catchError回調(diào)才能捕獲錯(cuò)誤戏自,then方法還有一個(gè)可選參數(shù)onError,我們也可以它來捕獲異常:
Future.delayed(new Duration(seconds: 2), () {
//return "hi world!";
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
Future.whenComplete
有些時(shí)候伤锚,我們會(huì)遇到無論異步任務(wù)執(zhí)行成功或失敗都需要做一些事的場(chǎng)景擅笔,比如在網(wǎng)絡(luò)請(qǐng)求前彈出加載對(duì)話框,在請(qǐng)求結(jié)束后關(guān)閉對(duì)話框屯援。這種場(chǎng)景猛们,有兩種方法,第一種是分別在then或catch中關(guān)閉一下對(duì)話框狞洋,第二種就是使用Future的whenComplete回調(diào)弯淘,我們將上面示例改一下:
Future.delayed(new Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//執(zhí)行成功會(huì)走到這里
print(data);
}).catchError((e){
//執(zhí)行失敗會(huì)走到這里
print(e);
}).whenComplete((){
//無論成功或失敗都會(huì)走到這里
});
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í)行成功后,才會(huì)觸發(fā)then的成功回調(diào)甲锡,只要有一個(gè)Future執(zhí)行失敗兆蕉,就會(huì)觸發(fā)錯(cuò)誤回調(diào)。下面缤沦,我們通過模擬Future.delayed 來模擬兩個(gè)數(shù)據(jù)獲取的異步任務(wù)虎韵,等兩個(gè)異步任務(wù)都執(zhí)行成功時(shí),將兩個(gè)異步任務(wù)的結(jié)果拼接打印出來缸废,代碼如下:
Future.wait([
// 2秒后返回結(jié)果
Future.delayed(new Duration(seconds: 2), () {
return "hello";
}),
// 4秒后返回結(jié)果
Future.delayed(new Duration(seconds: 4), () {
return " world";
})
]).then((results){
print(results[0]+results[1]);
}).catchError((e){
print(e);
});
執(zhí)行上面代碼包蓝,4秒后你會(huì)在控制臺(tái)中看到“hello world”驶社。
Async/await
先說一下async的用法,它作為一個(gè)關(guān)鍵字放到函數(shù)前面测萎,用于表示函數(shù)是一個(gè)異步函數(shù)亡电,因?yàn)閍sync就是異步的意思, 異步函數(shù)也就意味著該函數(shù)的執(zhí)行不會(huì)阻塞后面代碼的執(zhí)行硅瞧。
async function timeout(flag) {
if (flag) {
return 'hello world'
} else {
throw 'my god, failure'
}
}
console.log(timeout(true)) // 調(diào)用Promise.resolve() 返回promise 對(duì)象份乒。
console.log(timeout(false)); // 調(diào)用Promise.reject() 返回promise 對(duì)象。
timeout(false).catch(err => {
console.log(err)
})
Future相當(dāng)于Promise
//Promise 對(duì)象用于表示一個(gè)異步操作的最終狀態(tài)(完成或失斖筮蟆)或辖,以及其返回的值。
回調(diào)地獄****(Callback hell)
如果代碼中有大量異步邏輯枣接,并且出現(xiàn)大量異步任務(wù)依賴其它異步任務(wù)的結(jié)果時(shí)颂暇,必然會(huì)出現(xiàn)Future.then回調(diào)中套回調(diào)情況。舉個(gè)例子但惶,比如現(xiàn)在有個(gè)需求場(chǎng)景是用戶先登錄耳鸯,登錄成功后會(huì)獲得用戶Id,然后通過用戶Id膀曾,再去請(qǐng)求用戶個(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ù)邏輯中有大量異步依賴的情況喊废,將會(huì)出現(xiàn)上面這種在回調(diào)里面套回調(diào)的情況,過多的嵌套會(huì)導(dǎo)致的代碼可讀性下降以及出錯(cuò)率提高栗弟,并且非常難維護(hù)污筷,這個(gè)問題被形象的稱為回調(diào)地獄(****Callback hell****)≌Ш眨回調(diào)地獄問題在之前JavaScript中非常突出瓣蛀,也是JavaScript被吐槽最多的點(diǎn),但隨著ECMAScript6和ECMAScript7標(biāo)準(zhǔn)發(fā)布后雷厂,這個(gè)問題得到了非常好的解決惋增,而解決回調(diào)地獄的兩大神器正是ECMAScript6引入了Promise,以及ECMAScript7中引入的async/await改鲫。 而在Dart中幾乎是完全平移了JavaScript中的這兩者:Future相當(dāng)于Promise诈皿,而async/await連名字都沒改林束。接下來我們看看通過Future和async/await如何消除上面示例中的嵌套問題。
使用****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對(duì)象壶冒,所以可以很方便的進(jìn)行鏈?zhǔn)秸{(diào)用” ,如果在then中返回的是一個(gè)Future的話截歉,該future會(huì)執(zhí)行胖腾,執(zhí)行結(jié)束后會(huì)觸發(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ù)會(huì)返回一個(gè)Future對(duì)象酵使,可以使用then方法添加回調(diào)函數(shù)。
- await 后面是一個(gè)Future焙糟,表示等待該異步任務(wù)完成口渔,異步完成后才會(huì)往下走;await必須出現(xiàn)在 async函數(shù)內(nèi)部穿撮。
可以看到缺脉,我們通過async/await將一個(gè)異步流用同步的代碼表示出來了。
其實(shí)悦穿,無論是在JavaScript還是Dart中攻礼,async/await都只是一個(gè)語(yǔ)法糖,編譯器或解釋器最終都會(huì)將其轉(zhuǎn)化為一個(gè)Promise(Future)的調(diào)用鏈栗柒。
Stream
Stream 也是用于接收異步事件數(shù)據(jù)礁扮,和Future 不同的是,它可以接收多個(gè)異步操作的結(jié)果(成功或失斔猜佟)太伊。 也就是說,在執(zhí)行異步任務(wù)時(shí)逛钻,可以通過多次觸發(fā)成功或失敗事件來傳遞結(jié)果數(shù)據(jù)或錯(cuò)誤異常僚焦。 Stream 常用于會(huì)多次讀取數(shù)據(jù)的異步任務(wù)場(chǎng)景,如網(wǎng)絡(luò)內(nèi)容下載绣的、文件讀寫等叠赐。舉個(gè)例子:
Stream.fromFutures([
// 1秒后返回結(jié)果
Future.delayed(new Duration(seconds: 1), () {
return "hello 1";
}),
// 拋出一個(gè)異常
Future.delayed(new Duration(seconds: 2),(){
throw AssertionError("Error");
}),
// 3秒后返回結(jié)果
Future.delayed(new Duration(seconds: 3), () {
return "hello 3";
})
]).listen((data){
print(data);
}, onError: (e){
print(e.message);
},onDone: (){
});
上面的代碼依次會(huì)輸出:
I/flutter (17666): hello 1
I/flutter (17666): Error
I/flutter (17666): hello 3
dart語(yǔ)言-Flutter-Dart中的異步
Even-Looper
Dart是單線程模型欲账,也就沒有了所謂的主線程/子線程之分。Dart也是Event-Looper以及Event-Queue的模型芭概,所有的事件都是通過EventLooper的依次執(zhí)行赛不。
而Dart的Event Loop就是:
- 從EventQueue中獲取Event
- 處理Event
- 直到EventQueue為空
而這些Event包括了用戶輸入,點(diǎn)擊罢洲,Timer踢故,文件IO等
單線程模型
一旦某個(gè)Dart的函數(shù)開始執(zhí)行,它將執(zhí)行到這個(gè)函數(shù)結(jié)束惹苗,也就是Dart的函數(shù)不會(huì)被其他Dart代碼打斷殿较。Dart中沒有線程的概念,只有isolate桩蓉,每個(gè)isolate都是隔離的淋纲,并不會(huì)共享內(nèi)存。
而一個(gè)Dart程序是在Main isolate的main函數(shù)開始院究,而在Main函數(shù)結(jié)束后洽瞬,Main isolate線程開始一個(gè)一個(gè)(one by one)的開始處理Event Queue中的每一個(gè)Event。
Event Queue****以及****Microtask Queue
Dart中的Main Isolate只有一個(gè)Event Looper业汰,但是存在兩個(gè)Event Queue:Event Queue以及Microtask Queue
Microtask Queue存在的意義是:希望通過這個(gè)Queue來處理稍晚一些的事情伙窃,但是在下一個(gè)消息到來之前需要處理完的事情。
當(dāng)Event Looper正在處理Microtask Queue中的Event時(shí)候样漆,Event Queue中的Event就停止了處理了为障,此時(shí)App不能繪制任何圖形,不能處理任何鼠標(biāo)點(diǎn)擊放祟,不能處理文件IO等等
Event-Looper挑選Task的執(zhí)行順序?yàn)椋?/p>
- 優(yōu)先全部執(zhí)行完Microtask Queue中的Event
- 直到Microtask Queue為空時(shí)鳍怨,才會(huì)執(zhí)行Event Queue中的Event
Dart中只能知道Event處理的先后順序,但是并不知道某個(gè)Event執(zhí)行的具體時(shí)間點(diǎn)舞竿,因?yàn)樗奶幚砟P褪且粋€(gè)單線程循環(huán)京景,而不是基于時(shí)鐘調(diào)度(即它的執(zhí)行只是按照Event處理完,就開始循環(huán)下一個(gè)Event骗奖,而與Java中的Thread調(diào)度不一樣,沒有時(shí)間調(diào)度的概念)醒串,也就是我們既是指定另一個(gè)Delay Time的Task执桌,希望它在預(yù)期的時(shí)間后開始執(zhí)行,它有可能不會(huì)在那個(gè)時(shí)間執(zhí)行芜赌,需要看是否前面的Event是否已經(jīng)Dequeue仰挣。
異步任務(wù)調(diào)度
當(dāng)有代碼可以在后續(xù)任務(wù)執(zhí)行的時(shí)候,有兩種方式缠沈,通過dart:async這個(gè)Lib中的API即可:
- 使用Future類膘壶,可以將任務(wù)加入到Event Queue的隊(duì)尾
- 使用scheduleMicrotask函數(shù)错蝴,將任務(wù)加入到Microtask Queue隊(duì)尾
當(dāng)使用EventQueue時(shí),需要考慮清楚颓芭,盡量避免microtask queue過于龐大顷锰,否則會(huì)阻塞其他事件的處理
一般常用的Future構(gòu)造函數(shù):
new Future((){
// doing something
});
而一般常用的還有當(dāng)有分治任務(wù)時(shí),需要將一個(gè)大任務(wù)拆成很多小任務(wù)一步步執(zhí)行時(shí)亡问,就需要使用到Future.then函數(shù)來拆解任務(wù)
void main(){
new Future(() => futureTask) // 異步任務(wù)的函數(shù)
.then((m) => "futueTask execute result:$m") // 任務(wù)執(zhí)行完后的子任務(wù)
.then((m) => m.length) // 其中m為上個(gè)任務(wù)執(zhí)行完后的返回的結(jié)果
.then((m) => printLength(m))
.whenComplete(() => whenTaskCompelete); // 當(dāng)所有任務(wù)完成后的回調(diào)函數(shù)
}
int futureTask() {
return 21;
}
void printLength(int length) {
print("Text Length:$length");
}
void whenTaskCompelete() {
print("Task Complete");
}
串行任務(wù)執(zhí)行官紫。
當(dāng)任務(wù)需要延遲執(zhí)行時(shí),可以使用new Future.delay來將任務(wù)延遲執(zhí)行州藕,而如上所述束世,只有當(dāng)Main isolate的Event Queue處于Idle的狀態(tài)時(shí),才會(huì)延遲1s執(zhí)行床玻,否則等待的時(shí)間會(huì)比1s長(zhǎng)很多
new Future.delayed(const Duration(seconds: 1), () => futureTask);
- Future中的then并沒有創(chuàng)建新的Event丟到Event Queue中毁涉,而只是一個(gè)普通的Function Call,在FutureTask執(zhí)行完后锈死,立即開始執(zhí)行
- 當(dāng)Future在then函數(shù)先已經(jīng)執(zhí)行完成了薪丁,則會(huì)創(chuàng)建一個(gè)task,將該task的添加到microtask queue中馅精,并且該任務(wù)將會(huì)執(zhí)行通過then傳入的函數(shù)
- Future只是創(chuàng)建了一個(gè)Event严嗜,將Event插入到了Event Queue的隊(duì)尾
- 使用Future.value構(gòu)造函數(shù)的時(shí)候,就會(huì)和第二條一樣洲敢,創(chuàng)建Task丟到microtask Queue中執(zhí)行then傳入的函數(shù)
- Future.sync構(gòu)造函數(shù)執(zhí)行了它傳入的函數(shù)之后漫玄,也會(huì)立即創(chuàng)建Task丟到microtask Queue中執(zhí)行
使用****scheduleMicrotask
在最頂層的調(diào)用關(guān)系中年叮,使用該函數(shù)即可
async.scheduleMicrotask(() => microtask());
void microtask(){
// doing something
}
使用****isolate****以及****Worker
當(dāng)有計(jì)算很繁重的任務(wù)時(shí)彻磁,則需要使用isolate或者Worker來執(zhí)行,以保持App對(duì)用戶操作的及時(shí)響應(yīng)掠械。Isolate的實(shí)現(xiàn)可能是一個(gè)單獨(dú)的線程壮不,或者一個(gè)單獨(dú)的進(jìn)程汗盘,需要看Dart VM是如何實(shí)現(xiàn)的。