2019 年春节前最后一更了

在 dart 中有生成器函数的语法,在很多其他的语言中也有,比如 js c#

这个语法看上去和 async await 语法很像

使用的关键字是 async* sync* yield yield*

官方对于这个语法的说明可以参考这个连接generators

其实async await也是一种生成器语法

生成器语法就是你返回的类型通常情况下和 return 的类型可能不一致

比如你return 1,但是返回值上却需要写Future<int>

sync*

在 dart 中可以使用这个便利的生成一个迭代器

如下所示

20190203173018.png

这两种写法是一样的,但是第一个写法会简洁很多

main(List<String> arguments) {
  print(genList());
  print(genList2());
}

Iterable<int> genList({int max = 10}) sync* {
  var i = 0;
  while (i < max) {
    yield i;
    i++;
  }
}

Iterable<int> genList2({int max = 10}) {
  var list = <int>[];
  var i = 0;
  while (i < max) {
    list.add(i);
    i++;
  }
  return list.map((i) => i);
}

async*

这个返回值是一个 Stream

main(List<String> arguments) {
  print(genList());
  print(genList2());

  genStream().listen((data) {
    print("stream1 : $data");
  });

  genStream2().listen((data) {
    print("stream2 : $data");
  });
}


Stream<int> genStream({int max = 10}) async* {
  int i = 0;
  while (i < max) {
    yield i;
    await Future.delayed(Duration(milliseconds: 300));
    i++;
  }
}

Stream<int> genStream2({int max = 10}) {
  StreamController<int> controller = StreamController();

  Future<void>.delayed(Duration.zero).then((_) async {
    int i = 0;
    while (i < max) {
      controller.add(i);
      await Future.delayed(Duration(milliseconds: 300));
      i++;
    }
    controller.close();
  });

  return controller.stream;
}

两种写法达到了一样的效果,但是生成器函数代码会更加简洁一些

执行结果如下 20190203174030.png

yield*

在生成器函数中还有一个关键字 yield*

这个关键字是结合递归使用的,可以配合sync* 也可以配合async*

结合 sync*

main(List<String> arguments) {
  var r = naturalsDownFrom(10);
  print(r); //(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

  r = naturalsDownWithNormal(10);
  print(r); //(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
}

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

Iterable<int> naturalsDownWithNormal(int n) {
  var list = <int>[];
  if (n > 0) {
    list.add(n);
    var r = naturalsDownWithNormal(n - 1);
    list.addAll(r);
  }
  return list.map((v) => v);
}

结合 async*


main(List<String> arguments){
  naturalsStreamDownFrom(10).listen((data) {
    print("data = $data");
  });

}

Stream<int> naturalsStreamDownFrom(int n) async* {
  if (n > 0) {
    yield n;
    yield* naturalsStreamDownFrom(n - 1);
  }
}

输出结果

data = 10
data = 9
data = 8
data = 7
data = 6
data = 5
data = 4
data = 3
data = 2
data = 1

常规写法分开写

main(List<String> arguments) {
  naturalsStreamDownWithNormal(10).listen((data) {
    print("data2  = $data");
  });
}


Stream<int> naturalsStreamDownWithNormal(int n) {
  var controller = StreamController<int>();
  if (n > 0) {
    controller.add(n);
    naturalsStreamDownWithNormal(n - 1).listen((data) {
      controller.add(data);
    });
  }
  return controller.stream;
}

data2  = 10
data2  = 9
data2  = 8
data2  = 7
data2  = 6
data2  = 5
data2  = 4
data2  = 3
data2  = 2
data2  = 1

这里常规的写法也比较复杂,而且还有 controller 不关闭的可能

还需要注意一下 streamController 的关闭

需要修改如下


Stream<int> naturalsStreamDownWithNormal(int n) {
  var controller = StreamController<int>();
  if (n > 0) {
    controller.add(n);
    naturalsStreamDownWithNormal(n - 1).listen((data) {
      controller.add(data);
    }, onDone: () {
      print("close controller = $n");
      controller.close();
    });
  } else {
    controller.close();
  }
  return controller.stream;
}

这里加了一个 print 输出

close controller = 1
data2  = 10
close controller = 2
data2  = 9
close controller = 3
data2  = 8
close controller = 4
data2  = 7
close controller = 5
data2  = 6
close controller = 6
data2  = 5
close controller = 7
data2  = 4
close controller = 8
data2  = 3
close controller = 9
data2  = 2
close controller = 10
data2  = 1

日志是这样的,递归调用,结束后递归关闭

官方的说法是,使用yield*会有性能优化,所以还是建议使用生成器函数

后记

粗略的分析了一下生成器函数,记录下,为了以后的朋友能看到,同时最重要的是记录自己的学习过程

以上