前言
我們所熟悉的前端開發(fā)框架大都是事件驅(qū)動(dòng)的。事件驅(qū)動(dòng)意味著你的程序中必然存在事件循環(huán)和事件隊(duì)列。事件循環(huán)會(huì)不停的從事件隊(duì)列中獲取和處理各種事件。也就是說你的程序必然是支持異步的。
在Android中這樣的結(jié)構(gòu)是Looper/Handler;在iOS中是RunLoop;在JavaScript中是Event Loop。
同樣的Flutter/Dart也是事件驅(qū)動(dòng)的,也有自己的Event Loop。而且這個(gè)Event Loop和JavaScript的很像,很像。(畢竟Dart是想替換JS來著)。下面我們就來了解一下Dart中的Event Loop。
Dart的Event Loop
Dart的事件循環(huán)如下圖所示。和JavaScript的基本一樣。循環(huán)中有兩個(gè)隊(duì)列。一個(gè)是微任務(wù)隊(duì)列(MicroTask queue),一個(gè)是事件隊(duì)列(Event queue)。
- 事件隊(duì)列包含外部事件,例如I/O,
Timer
,繪制事件等等。 - 微任務(wù)隊(duì)列則包含有Dart內(nèi)部的微任務(wù),主要是通過
scheduleMicrotask
來調(diào)度。
Dart的事件循環(huán)的運(yùn)行遵循以下規(guī)則:
- 首先處理所有微任務(wù)隊(duì)列里的微任務(wù)。
- 處理完所有微任務(wù)以后。從事件隊(duì)列里取1個(gè)事件進(jìn)行處理。
- 回到微任務(wù)隊(duì)列繼續(xù)循環(huán)。
注意第一步里的所有,也就是說在處理事件隊(duì)列之前,Dart要先把所有的微任務(wù)處理完。如果某一時(shí)刻微任務(wù)隊(duì)列里有8個(gè)微任務(wù),事件隊(duì)列有2個(gè)事件,Dart也會(huì)先把這8個(gè)微任務(wù)全部處理完再從事件隊(duì)列中取出1個(gè)事件處理,之后又會(huì)回到微任務(wù)隊(duì)列去看有沒有未執(zhí)行的微任務(wù)。
總而言之,就是對(duì)微任務(wù)隊(duì)列是一次性全部處理,對(duì)于事件隊(duì)列是一次只處理一個(gè)。
這個(gè)流程要清楚,清楚了才能理解Dart代碼的執(zhí)行順序。
異步執(zhí)行
那么在Dart中如何讓你的代碼異步執(zhí)行呢?很簡單,把要異步執(zhí)行的代碼放在微任務(wù)隊(duì)列或者事件隊(duì)列里就行了。
- 可以調(diào)用
scheduleMicrotask
來讓代碼以微任務(wù)的方式異步執(zhí)行
scheduleMicrotask((){
print('a microtask');
});
- 可以調(diào)用
Timer.run
來讓代碼以Event的方式異步執(zhí)行
Timer.run((){
print('a event');
});
好了,現(xiàn)在你知道怎么讓你的Dart代碼異步執(zhí)行了。看起來并不是很復(fù)雜,但是你需要清楚的知道你的異步代碼執(zhí)行的順序。這也是很多前端面試時(shí)候會(huì)問到的問題。舉個(gè)簡單的例子,請(qǐng)問下面這段代碼是否會(huì)輸出"executed"?
main() {
Timer.run(() { print("executed"); });
foo() {
scheduleMicrotask(foo);
}
foo();
}
答案是不會(huì),因?yàn)樵谑冀K會(huì)有一個(gè)foo
存在于微任務(wù)隊(duì)列。導(dǎo)致Event Loop沒有機(jī)會(huì)去處理事件隊(duì)列。還有更復(fù)雜的一些例子會(huì)有大量的異步代碼混合嵌套起來然后問你執(zhí)行順序是什么樣的,這都需要按照上述Event Loop規(guī)則仔細(xì)去分析。
和JS一樣,僅僅使用回調(diào)函數(shù)來做異步的話很容易陷入“回調(diào)地獄(Callback hell)”,為了避免這樣的問題,JS引入了Promise
。同樣的, Dart引入了Future
。
Future
要使用Future
的話需要引入dart.async
import 'dart:async';
Future
提供了一系列構(gòu)造函數(shù)供你選擇。
創(chuàng)建一個(gè)立刻在事件隊(duì)列里運(yùn)行的Future
:
Future(() => print('立刻在Event queue中運(yùn)行的Future'));
創(chuàng)建一個(gè)延時(shí)1秒在事件隊(duì)列里運(yùn)行的Future
:
Future.delayed(const Duration(seconds:1), () => print('1秒后在Event queue中運(yùn)行的Future'));
創(chuàng)建一個(gè)在微任務(wù)隊(duì)列里運(yùn)行的Future
:
Future.microtask(() => print('在Microtask queue里運(yùn)行的Future'));
創(chuàng)建一個(gè)同步運(yùn)行的Future:
Future.sync(() => print('同步運(yùn)行的Future'));
對(duì),你沒看錯(cuò),同步運(yùn)行的。
這里要注意一下,這個(gè)同步運(yùn)行指的是構(gòu)造
Future
的時(shí)候傳入的函數(shù)是同步運(yùn)行的,這個(gè)Future
通過then
串進(jìn)來的回調(diào)函數(shù)是調(diào)度到微任務(wù)隊(duì)列異步執(zhí)行的。
有了Future
之后, 通過調(diào)用then
來把回調(diào)函數(shù)串起來,這樣就解決了"回調(diào)地獄"的問題。
Future(()=> print('task'))
.then((_)=> print('callback1'))
.then((_)=> print('callback2'));
在task打印完畢以后,通過then
串起來的回調(diào)函數(shù)會(huì)按照鏈接的順序依次執(zhí)行。
如果task執(zhí)行出錯(cuò)怎么辦?你可以通過catchError
來鏈上一個(gè)錯(cuò)誤處理函數(shù):
Future(()=> throw 'we have a problem')
.then((_)=> print('callback1'))
.then((_)=> print('callback2'))
.catchError((error)=>print('$error'));
上面這個(gè)Future
執(zhí)行時(shí)直接拋出一個(gè)異常,這個(gè)異常會(huì)被catchError
捕捉到。類似于Java中的try/catch
機(jī)制的catch
代碼塊。運(yùn)行后只會(huì)執(zhí)行catchError
里的代碼。兩個(gè)then
中的代碼都不會(huì)被執(zhí)行。
既然有了類似Java的try/catch
,那么Java中的finally
也應(yīng)該有吧。有的,那就是whenComplete
:
Future(()=> throw 'we have a problem')
.then((_)=> print('callback1'))
.then((_)=> print('callback2'))
.catchError((error)=>print('$error'))
.whenComplete(()=> print('whenComplete'));
無論這個(gè)Future
是正常執(zhí)行完畢還是拋出異常,whenComplete
都一定會(huì)被執(zhí)行。
以上就是對(duì)Future
的一些主要用法的介紹。Future
背后的實(shí)現(xiàn)機(jī)制還是有一些復(fù)雜的。這里先列幾個(gè)來自Dart官網(wǎng)的關(guān)于Future
的燒腦說明。大家先感受一下:
- 你通過then串起來的那些回調(diào)函數(shù)在
Future
完成的時(shí)候會(huì)被立即執(zhí) 行,也就是說它們是同步執(zhí)行,而不是被調(diào)度異步執(zhí)行。- 如果
Future
在調(diào)用then
串起回調(diào)函數(shù)之前已經(jīng)完成,
那么這些回調(diào)函數(shù)會(huì)被調(diào)度到微任務(wù)隊(duì)列異步執(zhí)行。- 通過
Future()
和Future.delayed()
實(shí)例化的Future
不會(huì)同步執(zhí)行,它們會(huì)被調(diào)度到事件隊(duì)列異步執(zhí)行。- 通過
Future.value()
實(shí)例化的Future
會(huì)被調(diào)度到微任務(wù)隊(duì)列異步完成,類似于第2條。- 通過
Future.sync()
實(shí)例化的Future
會(huì)同步執(zhí)行其入?yún)⒑瘮?shù),然后(除非這個(gè)入?yún)⒑瘮?shù)返回一個(gè)Future
)調(diào)度到微任務(wù)隊(duì)列來完成自己,類似于第2條。
從上述說明可以得出結(jié)論,Future
中的代碼至少會(huì)有一部分被異步調(diào)度執(zhí)行的,要么是其入?yún)⒑瘮?shù)和回調(diào)被異步調(diào)度執(zhí)行,要么就只有回調(diào)被異步調(diào)度執(zhí)行。
不知道大家注意到?jīng)]有,通過以上那些Future
構(gòu)造函數(shù)生成的Future
對(duì)象其實(shí)控制權(quán)不在你這里。它什么時(shí)候執(zhí)行完畢只能等系統(tǒng)調(diào)度了。你只能被動(dòng)的等待Future
執(zhí)行完畢然后調(diào)用你設(shè)置的回調(diào)。如果你想手動(dòng)控制某個(gè)Future
怎么辦呢?請(qǐng)使用Completer
。
Completer
這里就舉個(gè)Completer
的例子吧
// 實(shí)例化一個(gè)Completer
var completer = Completer();
// 這里可以拿到這個(gè)completer內(nèi)部的Future
var future = completer.future;
// 需要的話串上回調(diào)函數(shù)。
future.then((value)=> print('$value'));
//做些其它事情
...
// 設(shè)置為完成狀態(tài)
completer.complete("done");
上述代碼片段中,當(dāng)你創(chuàng)建了一個(gè)Completer
以后,其內(nèi)部會(huì)包含一個(gè)Future
。你可以在這個(gè)Future
上通過then
, catchError
和whenComplete
串上你需要的回調(diào)。拿著這個(gè)Completer
實(shí)例,在你的代碼里的合適位置,通過調(diào)用complete
函數(shù)即可完成這個(gè)Completer
對(duì)應(yīng)的Future
。控制權(quán)完全在你自己的代碼手里。當(dāng)然你也可以通過調(diào)用completeError
來以異常的方式結(jié)束這個(gè)Future
。
總結(jié)就是:
- 我創(chuàng)建的,完成了調(diào)我的回調(diào)就行了: 用
Future
。 - 我創(chuàng)建的,得我來結(jié)束它: 用
Completer
。
Future
相對(duì)于調(diào)度回調(diào)函數(shù)來說,緩減了回調(diào)地獄的問題。但是如果Future
要串起來的的東西比較多的話,代碼還是會(huì)可讀性比較差。特別是各種Future
嵌套起來,是比較燒腦的。
所以能不能更給力一點(diǎn)呢?可以的!JavaScript有 async/await,Dart也有。
async/await
async
和await
是什么?它們是Dart語言的關(guān)鍵字,有了這兩個(gè)關(guān)鍵字,可以讓你用同步代碼的形式寫出異步代碼。啥意思呢?看下面這個(gè)例子:
foo() async {
print('foo E');
String value = await bar();
print('foo X $value');
}
bar() async {
print("bar E");
return "hello";
}
main() {
print('main E');
foo();
print("main X");
}
函數(shù)foo
被關(guān)鍵字async
修飾,其內(nèi)部的有3行代碼,看起來和普通的函數(shù)沒什么兩樣。但是在第2行等號(hào)右側(cè)有個(gè)await
關(guān)鍵字,await
的出現(xiàn)讓看似會(huì)同步執(zhí)行的代碼裂變?yōu)閮刹糠帧H缦聢D所示:
綠框里面的代碼會(huì)在
foo
函數(shù)被調(diào)用的時(shí)候同步執(zhí)行,在遇到await
的時(shí)候,會(huì)馬上返回一個(gè)Future
,剩下的紅框里面的代碼以then
的方式鏈入這個(gè)Future
被異步調(diào)度執(zhí)行。
上述代碼運(yùn)行以后在終端會(huì)輸出如下:
可見
print('foo X $value')
是在main
執(zhí)行完畢以后才打印出來的。的確是異步執(zhí)行的。
而以上代碼中的foo
函數(shù)可以以Future
方式實(shí)現(xiàn)如下,兩者是等效的
foo() {
print('foo E');
return Future(bar).then((value) => print('foo X $value'));
}
await
并不像字面意義上程序運(yùn)行到這里就停下來啥也不干等待Future
完成。而是立刻結(jié)束當(dāng)前函數(shù)的執(zhí)行并返回一個(gè)Future
。函數(shù)內(nèi)剩余代碼通過調(diào)度異步執(zhí)行。
-
await
只能在async
函數(shù)中出現(xiàn)。 -
async
函數(shù)中可以出現(xiàn)多個(gè)await
,每遇見一個(gè)就返回一個(gè)Future
, 實(shí)際結(jié)果類似于用then
串起來的回調(diào)。 -
async
函數(shù)也可以沒有await
, 在函數(shù)體同步執(zhí)行完畢以后返回一個(gè)Future
。
使用async
和await
還有一個(gè)好處是我們可以用和同步代碼相同的try
/catch
機(jī)制來做異常處理。
foo() async {
try {
print('foo E');
var value = await bar();
print('foo X $value');
} catch (e) {
// 同步執(zhí)行代碼中的異常和異步執(zhí)行代碼的異常都會(huì)被捕獲
} finally {
}
}
在日常使用場景中,我們通常利用async
,await
來異步處理IO,網(wǎng)絡(luò)請(qǐng)求,以及Flutter中的Platform channels通信等耗時(shí)操作。
總結(jié)
本文大致介紹了Flutter/Dart中的異步運(yùn)行機(jī)制,從異步運(yùn)行的基礎(chǔ)(Event Loop)開始,首先介紹了最原始的異步運(yùn)行機(jī)制,直接調(diào)度回調(diào)函數(shù);到Future
;再到 async
和await
。了解了Flutter/Dart中的異步運(yùn)行機(jī)制是如何一步一步的進(jìn)化而來的。對(duì)于一直從事Native開發(fā),不太了解JavaScrip的同學(xué)來講,這個(gè)異步機(jī)制和原生開發(fā)有很大的不同,需要多多動(dòng)手練習(xí),動(dòng)腦思考才能適應(yīng)。本文中介紹的相關(guān)知識(shí)點(diǎn)較為粗淺,并沒有涉及dart:async
中關(guān)于Future
實(shí)現(xiàn)的源碼分析以及Stream
等不太常用的類。這些如果大家想了解一下的話我會(huì)另寫文章來介紹一下。