Generator functions may be unfamiliar and not used very often in normal development, but because they are an integral part of Dart asynchronous programming, I’ll cover them here. A generator function is a function that is used to delay the generation of a sequence of values, and there are two main types of generator functions in Dart: synchronous and asynchronous. We know that variables of type int (synchronous) or Future
(asynchronous) can only produce a single value, whereas Iterable
(synchronous) or Stream
(asynchronous) can produce multiple values. A synchronous generator function generates values immediately on demand and does not wait like a Future,Stream, whereas an asynchronous generator function generates values asynchronously, meaning it has enough time to generate values.
Single value | Zero or more value | |
---|---|---|
Sync (synchronous) | int | 可迭代 |
Async (asynchronous) | Future | Stream |
1. Why are generator functions needed
Generator functions are not often used in Flutter or Dart development. However, when it is used, generator functions are quite simple because it is cumbersome to implement value generators manually. For example, to implement a synchronous generator, you need to define an iterable class that inherits from IterableBase and override the iterator method to return a new Iterator object. To do this, you need to declare your own iterator class. You also need to implement the member method moveNext and the member attribute current to determine if you are iterating to the end. This is still writing a synchrogenerator step, the whole process is a bit cumbersome. So Dart gives you a way to generate a synchrogenerator function directly, simplifying the whole implementation step. However, implementing an asynchronous generator manually is far more complex than implementing a synchronous generator, so providing a simple asynchronous generator function directly makes development more efficient and allows you to focus on the business. Here’s an example of a synchrogenerator function:
Speaking of synchro generator functions, let’s review Iterable and Iterator
//Iterator can iterate over a series of values
abstract class Iterator<E> {
bool moveNext();// Indicates whether the iterator has a next value. The iterator loads the next value as the current value until the next value is empty and returns false
E get current; // Returns the value of the current iteration, that is, the most recent iteration
}
Copy the code
Implementing an Iterable manually by following the steps described above is actually quite simple, simply wrapping a shell around it
class StringIterable extends 可迭代<String> {
final List<String> stringList; // List is an Iterable
StringIterable(this.stringList);
@override
Iterator<String> get iterator =>
stringList.iterator; // Assign the List iterator to the iterator
}
// Then StringIterable is an iterator of a particular type of String, and we can iterate through it using a for-in loop
void main() {
var stringIterable = StringIterable([
"Dart"."Java"."Kotlin"."Swift"
]);
for (var value in stringIterable) {
print('$value'); }}// You can even combine StringIterable with operators such as map, WHERE, and reduce to transform iterator values
void main() {
var stringIterable = StringIterable([
"Dart"."Java"."Kotlin"."Swift"
]);
stringIterable
.map((language) => language.length)// Can be used in conjunction with map, essentially Iterable object conversion, StringIterable to MappedIterable
.forEach(print);
}
Copy the code
Output result: You can see that the above is not really a strictly manual implementation of one可迭代
Then the problem is how to implement a synchrogenerator function manually. Note:The synchrogenerator function must return one可迭代
Type, and then need to usesync*
Decorates the function to mark it as a synchrogenerator function.
void main() {
final numbers = getRange(1.10);
for (var value in numbers) {
print('$value'); }}// Use to generate a synchronization sequence
可迭代<int> getRange(int start, int end) sync* { //sync* tells Dart that this function is a sync generator function that produces values on demand
for (int i = start; i <= end; i++) {
yield i;The yield keyword is a bit like return, but it returns a single value and does not end the function directly like return}}Copy the code
Output result:The comparison shows that the generator function is really simple and convenient, and only needs to be passedsync*
和 yield
The keyword implements an iterator of any type, much simpler than manually implementing a synchro generator function, so you should know why generator functions are needed. Asynchronous generator functions are similar.
What is a Generator?
A generator function is a function that can easily delay the generation of a sequence of values. There are two main types of generators: ** synchronous generator function and asynchronous generator function. The synchronous generator, which is decorated with the sync* keyword, returns an Iterable object representing a collection of values that can be accessed sequentially. Asynchronous generator functions need to be decorated with the async* keyword and return a Stream object representing an asynchronous data event. ** In addition, synchronous generator functions generate values immediately on demand and do not wait like Future,Stream, whereas asynchronous generator functions generate values asynchronously, that is, they have enough time to generate values.
2.1 Synchronous Generator (Synchronous Generator)
Synchrogenerator functions need to work togethersync*
Key words andyield
Keyword, the final return is a可迭代
Object, whereyield
The keyword is used to return a single value each time, and it is important to note that its return does not end the entire function.
import 'dart:io';
void main() {
final numbers = countValue(10);
for (var value in numbers) {
print('$value'); }}可迭代<int> countValue(int max) sync* {//sync* tells Dart that this function is a sync generator function that produces values on demand
for (int i = 0; i < max; i++) {
yield i; // the yield keyword returns a single value each time
sleep(Duration(seconds: 1)); }}Copy the code
Output result:
2.2 Asynchronous Generator (Asynchronous Generator)
Asynchronous generator functions need to work togetherasync*
Key words andyield
Keyword, the final return is aStream
Object, notice thatGenerating a Stream is also a Stream of a single subscription model,That is to say, you can’t have multiple listeners at the same time or you will get an exception if you want to implement support for multiple listenersasBroadcastStream
Convert a Stream to a broadcast subscription model.
import 'dart:io';
void main() {
Stream<int> stream = countStream(10);
stream.listen((event) {
print('event value: $event');
}).onDone(() {
print('is done');
});
}
Stream<int> countStream(int max) async* {
//async* tells Dart that this function is an asynchronous generator function that generates an asynchronous event stream
for (int i = 0; i < max; i++) {
yield i;
sleep(Duration(seconds: 1)); }}Copy the code
Output result:
2.3 Yield Keyword
The yield keyword is used in generator functions to generate each value in order. It is similar to a return statement, but unlike a return statement, one yield does not end the entire function. Instead, it provides a single value at a time, suspends (not blocks) for the caller to request the next value, and then executes again, as in the example for loop above, where each execution of the loop triggers the yield execution to return each single value once and enter the wait for the next loop.
可迭代<int> countValue(int max) sync* {//sync* tells Dart that this function is a sync generator function that produces values on demand
for (int i = 0; i < max; i++) {
yield i; // Each iteration of the loop triggers the current yield and then the next wait
sleep(Duration(seconds: 1)); }}Copy the code
2.4 Yield * keyword
The yield keyword is used to produce a value in a loop followed by a specific object or value, but the yield keyword is followed by a function, usually a recursive function, that performs a function similar to a quadratic recursive function. Although the yield keyword can also implement a quadratic recursive function but is more complex, it is much simpler if used.
// Use yield to implement a quadratic recursive function
可迭代 naturals(n) sync* {
if (n > 0) {
yield n;
for (int i in naturals(n- 1)) {
yieldi; }}}// This is a quadratic recursive function implemented using yield*
可迭代 naturals(n) sync* {
if ( n > 0) {
yield n;
yield* naturals(n- 1); }}Copy the code
2.5 await for
As we said in the Stream article, for a synchronous iterator we can use a for-in loop to iterate over each element, while for an asynchronous Stream we can well use await for to iterate over each event element.
void main() async {
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (int value) {
return value + 1;
});
await stream.forEach((element) => print('stream value is: $element'));
}
Copy the code
3. How to use Generator functions
3.1 sync* + yield Implements the synchronization generator
void main() {
final 可迭代<int> sequence = countDownBySync(10);
print('start');
sequence.forEach((element) => print(element));
print('end');
}
可迭代<int> countDownBySync(int num) sync* {//sync*
while (num > 0) {
yield num-;/ / yield return values}}Copy the code
Output result:
3.2 Async * + yield Implements an asynchronous generator
void main() {
final Stream<int> sequence = countDownByAsync(10);
print('start');
sequence.listen((event) => print(event)).onDone(() => print('is done'));
print('end');
}
Stream<int> countDownByAsync(int num) async* {//async* returns a Stream asynchronously
while (num > 0) {
yield num-; }}Copy the code
Output result:
3.3 sync* + yield* Implements recursive synchronization generator
void main() {
final 可迭代<int> sequence = countDownBySyncRecursive(10);
print('start');
sequence.forEach((element) => print(element));
print('end');
}
可迭代<int> countDownBySyncRecursive(int num) sync* {
if (num > 0) {
yield num;
yield* countDownBySyncRecursive(num - 1);//yield* followed by a recursive function}}Copy the code
Output result:
3.4 Async * + yield* Implements recursive asynchronous generators
void main() {
final Stream<int> sequence = countDownByAsync(10);
print('start');
sequence.listen((event) => print(event)).onDone(() => print('is done'));
print('end');
}
Stream<int> countDownByAsyncRecursive(int num) async* {
if (num > 0) {
yield num;
yield* countDownByAsyncRecursive(num - 1);//yield* followed by a recursive function}}Copy the code
Output result:
4. Usage scenarios of Generator functions
There isn’t a lot of information about the use of generator functions in the development process, but that doesn’t mean there aren’t any scenarios for using generator functions, otherwise this article would be pointless. In fact, if you have any experience with Flutter development, there is a Bloc state management framework that makes extensive use of asynchronous generator functions. Take a look:
class CountBloc extends Bloc<CounterEvent.int> {
@override
int get initialState => 0;
@override
Stream<int> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
yield state - 1;
break;
case CounterEvent.increment:
yield state + 1;
break; }}}Copy the code
In fact, in addition to what is said above Bloc state management framework used in the scene, there may be a scenario that is asynchronous quadratic function recursive scenarios, such as implement a animation trajectory calculation, actually are simulated by some quadratic function calculation, so this time the generator is recursive functions can come in handy. While generator functions don’t use scenarios very often, they need to be the first thing that comes to mind to make a particular scenario easier to implement.
5. To summarize
This concludes the series on Flutter asynchronous programming. The next series will cover Dart annotations +APT generation code techniques. Although generator functions are not used much in the development process, they are also used as part of the Flutter asynchronism, and there are certain scenarios that can be easily solved by using them.
Thank you for your attention, Mr. Xiong Meow is willing to grow up with you on the technical road!