async *是否会延迟调用该函数?

时间:2019-04-28 19:06:44

标签: asynchronous dart generator

在为项目编写一些单元测试期间,我遇到了一个有趣的问题。

这是用户可以用来放置标记的地图:

class DomainMap {
  static const _DEFAULT_COORDINATE = const Coordinate(40.73, -73.93);
  final ReverseGeocodingStrategy _geocodingStrategy;
  final RouteDefinitionStrategy _assemblyStrategy;
  final List<_IdentifiedCoordinate> _addressed = [];
  final List<Coordinate> _markers = [];
  final _Route _route = _Route();

  Coordinate get defaultCoordinate => _DEFAULT_COORDINATE;

  DomainMap(this._geocodingStrategy, this._assemblyStrategy);

  Stream<MarkersUpdateEvent> mark(Coordinate coordinate) async* {
    _markers.add(coordinate);
    yield _assembleMarkersUpdate();
    final Address address = await _geocodingStrategy.geocode(coordinate);
    _addressed.add(_IdentifiedCoordinate(coordinate, address));
    if (_addressed.length > 1) {
      final Iterable<Coordinate> assembledPolyline =
          await _assemblyStrategy.buildRoute(BuiltList(_addressed
              .map((identifiedCoordinate) => identifiedCoordinate.address)));
      assembledPolyline.forEach(_route.add);
      yield _assembleMarkersUpdate();
    }
  }

  MarkersUpdateEvent _assembleMarkersUpdate() =>
      MarkersUpdateEvent(BuiltList.from(_markers), _route.readOnly);
}

class _Route {
  final List<Coordinate> _points = [];

  Iterable<Coordinate> get readOnly => BuiltList(_points);

  void add(final Coordinate coordinate) => _points.add(coordinate);

  void addAll(final Iterable<Coordinate> coordinate) => _points.addAll(coordinate);
}

这是一个单元测试,它检查第二个标记处是否应该返回一条路由:

test("mark, assert that on second mark at first just markers update is published, and then the polyline update too", () async {
  final Coordinate secondCoordinate = plus(givenCoordinate, 1);
  final givenRoute = [
    givenCoordinate,
    minus(givenCoordinate, 1),
    plus(givenCoordinate, 1)
  ];
  when(geocodingStrategy.geocode(any)).thenAnswer((invocation) => Future.value(Address(invocation.positionalArguments[0].toString())));
  when(assemblyStrategy.buildRoute(any))
    .thenAnswer((_) => Future.value(givenRoute));
  final expectedFirstUpdate =
    MarkersUpdateEvent([givenCoordinate, secondCoordinate], []);
  final expectedSecondUpdate =
    MarkersUpdateEvent([givenCoordinate, secondCoordinate], givenRoute);
  final DomainMap map = domainMap();
  map.mark(givenCoordinate)
  //.forEach(print) //Important
  ;
  expect(map.mark(secondCoordinate),
    emitsInOrder([expectedFirstUpdate, expectedSecondUpdate]));
}, timeout: const Timeout(const Duration(seconds: 10)));

当我这样运行时,测试失败,并说该流仅发出一个值-一个更新事件,其中只有markers字段不为空,仅包含secondCoordinate。但是当我取消注释forEach时,测试就通过了。

据我了解-async*方法直到流的值不被请求时才被调用,因此,当forEach被调用时-函数将一直执行到最后。因此,如果我请求所有流(从第一个调用返回的)值-执行该方法,填充markers列表,并以预期状态执行第二个执行。

我是否正确理解async*的语义?并且在这里有一种使此函数渴望而不是懒惰的方法(我不想请求不需要的流的值)吗?

1 个答案:

答案 0 :(得分:2)

是的,在返回的流上调用async*后,listen会延迟调用该函数。如果您从不听,那么什么也不会发生。 它甚至异步执行此操作,而不是直接响应listen调用。

因此,如果您确实需要发生某些事情,但是可能只需要查看响应,那么您就无法使用async*函数来完成某些事情。

您可能想要做的是有条件地 填充流,但前提是实际收听了该流。 这是一个非传统的操作序列,与async*甚至async语义都不匹配。 您必须为操作完成做好准备,然后稍后再收听流。这建议将操作分为两部分,一个用于请求async,一个用于响应async*,并在两者之间共享前途,这意味着两次收听同一前途,这是一个明显的非-async的行为。

我建议拆分流行为,并为此使用StreamController

Stream<MarkersUpdateEvent> mark(Coordinate coordinate) {
  var result = StreamController<MarkersUpdateEvent>();
  () async {
    _markers.add(coordinate);
    result.add(_assembleMarkersUpdate());
    final Address address = await _geocodingStrategy.geocode(coordinate);
    _addressed.add(_IdentifiedCoordinate(coordinate, address));
    if (_addressed.length > 1) {
      final Iterable<Coordinate> assembledPolyline =
          await _assemblyStrategy.buildRoute(BuiltList(_addressed
              .map((identifiedCoordinate) => identifiedCoordinate.address)));
      assembledPolyline.forEach(_route.add);
      result.add(_assembleMarkersUpdate());
    }
    result.close();
  }().catchError(result.addError);
  return result.stream;
}

这样,程序逻辑独立于是否有人收听流而运行。您仍然可以缓冲所有流事件。没有真正的方法可以避免这种情况,除非您以后可以计算它们,因为您不知道某人何时可以收听返回的流。返回时不必立即发生。