平時我們在寫業務邏輯的時候,肯定都會與網路打交道,那肯定也就避免不了非同步請求,程式碼類似如下:
int getData() async {
Response r = await Dio().get('https://www.baidu.com');
return r.data;
}
複製程式碼
這段程式碼相信很多人都非常非常熟悉了,我們也都知道 async 是什麼意思,那加上一個星號,你還知道嗎?
加上星號其實就是「函式生成器」的意思。
那我們先從「sync/sync*」說起。
sync/sync*
「sync」我們都知道是預設程式執行的狀態,舉個例子:
foo1 (){
print('foo1 start');
for(int i = 0; i < 3; i++){
print(i);
}
print('foo1 stop');
}
複製程式碼
當我們在 main
函式裡執行,結果大家應該都很清楚:
foo1 start
0 1 2
foo1 stop
那所謂的函式生成器呢?
被「sync*」標記的函式,一定要返回一個 「Iterable」,這樣的函式生成器叫做同步生成器:
Iterable<int> foo2() sync*{
print('foo2 start');
for(int i = 0; i < 3; i++){
print('執行了foo2,當前index:${i}');
yield i;
}
print('foo2 stop');
}
複製程式碼
這回我們在 main
函式裡執行 foo2()
,會出現什麼效果?
答案是什麼也不會發生,print也沒有列印。
這是為什麼?
當我們呼叫 foo2()
的時候,這裡會馬上返回一個 Iterable
,就像網路請求會馬上返回一個 Feature
一樣。
但是在我們沒有呼叫 Iterable
的 moveNext
的時候,當前函式體是不會執行的。
而當我們呼叫了 moveNext
方法後,程式碼會執行到 yield
關鍵字的位置,並且在這裡停住。
當我們再一次呼叫 moveNext
後,會再恢復執行,然後再次停到 yield
關鍵字的位置,依次迴圈,當沒有下一個值得時候,函式會隱式的呼叫 return方法來終止函式。
來看一下呼叫方式和結果:
var b = foo2().iterator;
print('還沒開始呼叫 moveNext');
b.moveNext();
print('第${b.current}次moveNext');
b.moveNext();
print('第${b.current}次moveNext');
b.moveNext();
print('第${b.current}次moveNext');
複製程式碼
結果為:
還沒開始呼叫 moveNext
foo2 start
執行了foo2,當前index:0
第0次moveNext
執行了foo2,當前index:1
第1次moveNext
執行了foo2,當前index:2
第2次moveNext
複製程式碼
從執行結果上來看,我們的說法是正確的,下面就來說一下非同步生成器。
async/async*
說非同步生成器之前,先來說一下普通的非同步呼叫。
現在有一個這樣的需求,我想每隔一秒鐘請求一下資料,一共請求10次,看看有沒有人關注我等等,
如果使用原始的 async,該怎麼做?
getData() async {
for (int i = 0; i < 10; i++){
await Future.delayed(Duration(seconds: 1), ()async {
Data data = await getXXX();
setState(){
//業務邏輯
};
});
}
}
複製程式碼
這裡使用迴圈,然後每一秒鐘請求依次介面,返回資料後 setState();
這樣肯定不行,因為你不可能一兩秒鐘就 setState()一次,
這個時候 async* 就派上用場了:
Stream<Data> getData() async* {
for (int i = 0; i < 10; i++){
await Future.delayed(Duration(seconds: 1));
yield await getXXX();
}
}
複製程式碼
在頁面上,我們可以用 StreamBuilder
來包住,這樣每次返回資料就不用 setState() 了。
總結
其實函式生成器可能一年都用不上一兩次,但是當你用到之後,就會覺得真的很舒服。
其實我個人認為這種函式生成器還有一種作用就是可以讓一個函式返回多個值。
另我個人建立了一個「Flutter 交流群」,可以新增我個人微信 「17610912320」來入群。
推薦閱讀: