Search code examples
flutterdartdart-async

How to work with async* functions in Dart


I am using flutter_bloc library.

In the bloc, the mapEventToState method is an async* function which returns Stream<BlocState>. From this function I am calling other async* functions like this yield* _handleEvent(event)

In such method, I am calling some Future returns functions but in the Future then() function it wont let me call other yield* functions.

Here is an example:

Stream<BlocState> mapEventToState(BlocEvent event) async*{
     yield* _handlesEvent(event); //This calls to worker method 
}

Stream<BlocState> _handleEvent(BlocEvent event) async* {
   _repository.getData(event.id).then((response) async* { //Calling Future returned function
         yield* _processResult(response); //This won't work
     }).catchError((e)  async* {
         yield* _handleError(e);  //This won't work either
     });

   Response response = await _repository.getData(event.id); //This do works but I want to use it like above, is it possible?
   yield* _processResult(response); //This do works
}

The question is however, how to combine between Future and Stream in dart. I could use await _repository.getData which works. but then I won't catch the error.


Solution

    1. await is just syntactic sugar for .then(), and putting await in a try-catch block is syntactic sugar for using .catchError. Things that you can do one way can be done with the other.

    2. In your first version that uses .then()/.catchError(), your function doesn't return anything.

    3. Your callbacks won't work because you're using yield* in them, but you haven't specified the callbacks with sync* or async*. To avoid name collisions, the yield keyword requires them (in the same way that await requires a function use async or async*).

    Here's a version that should work with .then() and .catchError():

    Stream<BlocState> _handleEvent(BlocEvent event) async* {
      yield* await _repository.getData(event.id).then((response) async* {
        yield* _processResult(response);
      }).catchError((e) async* {
        yield* _handleError(e);
      });
    }
    

    Note that the callbacks don't need to use yield*; they could just return their Streams directly:

    Stream<BlocState> _handleEvent(BlocEvent event) async* {
      yield* await _repository.getData(event.id).then((response) {
        return _processResult(response);
      }).catchError((e) {
        return _handleError(e);
      });
    }
    

    But (as everyone else has noted) using await instead of the Future API simplifies the whole thing (especially since we're already using await anyway):

    Stream<BlocState> _handleEvent(BlocEvent event) async* {
      try 
        response = await _repository.getData(event.id);
        yield* _processResult(response);
      } catch (e) {
        yield* _handleError(e);
      }
    }
    

    See https://dartpad.dartlang.org/fc1ff92e461754bdb35b998e7fbb3406 for a runnable example.