iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 27
0
Software Development

Dart 語言 - 開啟 Flutter 的鑰匙系列 第 27

Day 27:讓產生器 (Generator) 來產生一連串的同步或異步資料吧。

如果想要產生一個一組一連串的數值,Dart 提供了產生器函數 (Generator Function)。

產生器函數有兩種,一種是同步產生器 (Synchronous generator),另一種是異步產生器 (Asynchornous generator)。

  • 同步產生器回傳 Iterable 物件。
  • 異步產生器回傳 Stream 物件。

例如:

單一數值 零或更多數值
同步產生器 int Iterable<int>
異步產生器 Future <int> Stream <int>
  • 由上方的表格可以得知,同步產生器是將一般的型別轉換成 Iterable 的型別;異步產生器則是將 Future 數值轉成 Stream 數值。

同步產生器

介紹同步產生器之前,先介紹 Iterator<E> 以及 Iterable<E>

Iterator

迭代器 (Iterator) 是一個簡單的介面,可以迭代一系列的值。

abstract class Iterator<E>{
	bool moveNext();
	E get current;
}
  • current:取得目前的元素。
  • moveNext:移動到下一個,如果沒有下一個則回傳 false

Iterable

讓某類別能夠提供特定類型的迭代器。如前面介紹過的 List

如何使用 ?

例如: MyStrings 類別擴展了 Iterable<String> ,所以它需要一個迭代器屬性(Iterator get iterator => string.iterator;)

class MyStrings extends Iterable<String>{
	MyStrings(this.strings);
	final List<String> strings;

	Iterator<String> get iterator => 
			string.iterator;
}

接者,我們就可以使用 for-in 來迭代走訪迭代器裡的每一個元素。

void main(){
	final myStrings = MyStrings([
		'How',
		'Are',
		'You'
	]);
	for (final str in myStrings){
		print(str);
	}
}
//How
//Are
//You
  • 使用 for-in 迴圈,Dart 會自動使用迭代器裡的 moveNext 以及 current。

除了可以使用 for-in 迴圈之外,也可以使用 forEach 來迭代走訪每一個元素。

myStrings.forEach((value) => print(str);)

sync* 以及 yield 關鍵字

有了 IterableIterator 的概念之後,我們來看看怎麼使用同步產生器。

利用關鍵字 sync* 將函式定義為同步產生器,再利用關鍵字 yield 返回一個值。

範例:

Iterable<int> getRange(int start, int finish) sync*{
	for(int i = start; i <= finish; i++ ){
		yield i;
	}
}
  • 當呼叫這個函數之後,裡面的 for 迴圈才會開始啟動,然後迭代出數值。
void main(){
	final numbers = getRange(1, 5);
	
	for(final val in numbers){
		print(val);
	}
}
//1
//2
//3
//4
//5

產生器含有遞迴

Iterable<int> getRange(int start, int finish) sync*{
	if(start <= finish){
		yield start;
	
		for(final val in getRange(start+1, finish)){
			yield val;
		}
	}
}

可以將遞迴改為

Iterable<int> getRange(int start, int finish) sync*{
	if(start <= finish){
		yield start;
		yield* getRange(start + 1, finish);
	}
}
  • 用關鍵字 yield* 可以在每一次的遞迴取得一個值,而不需要 for 迴圈。

異步產生器

在 Stream 章節有提到,可以使用 async* 來建立 Stream<T> 。這就是異步產生器。

我們知道,Stream 就是一連串的 Future。

例如:

有一個函數 fetchString() 會回傳:

Future<String> fetchString(String str) async{
  return Future.value(str);
}
  • 那麼,如果呼叫此函數就可以取得一個 Future<String> 的值,並且可以使用 then 當 Future 結束之後取到真實的值。
void main(){
  fetchString("test").then((value) => print(value));
}
//test

如果需要得到一連串的 Future 呢?

可以使用 async* 來回傳一個 Stream 。

Stream<String> fetchStrings(List<String> strings) async* {
   for(var string in strings){
     yield await getString(string);
   }
}

同樣的,我們可以使用遞迴的方式取得每一個 Stream 的值

Stream<String> fetchStrings(List<String> strings) async* {
   yield* fetchStrings(strings);
}
  • 在遞迴中,使用關鍵字 yield* 來取得遞迴時每一個 Future 的值

小結

利用產生器我們可以產生一連串的同步資料或是異步資料,而不需要使用基本型態或是其他類別。

關鍵字 sync* 用來產生一連串的同步資料;關鍵字 async* 則是用來取得一連串的異步資料。

同步資料產生後,其型別為 Iterable 可以使用 for-in 或是 forEach 取得所有的元素。

異步資料產生之後,其型別為 Stream 可以使用 listener 來取得每一個 Future 元素。

另外,使用 yield* 加上遞迴的函數,那麼就可以直接用一行程式碼取得同步或異步的資料。


上一篇
Dart 26:用 import 匯入其他函式庫
下一篇
Day 28:測試你的代碼
系列文
Dart 語言 - 開啟 Flutter 的鑰匙30

尚未有邦友留言

立即登入留言