Bloc: .add is asynchronous?

Created on 27 Oct 2019  路  14Comments  路  Source: felangel/bloc

Describe the bug
inside my eventToState i want to call both yield newstate, and add (former dispatch) interchangeably in any order, and expect the state to update in the same order. Instead it seems that .add is asyncronous and state it produces arrives in wrong order.

To Reproduce
Steps to reproduce the behavior:
consider this piece of eventToState function

case UserGetEvent:
  add(UserFetchEvent()); // this state arrives second, while its clearly first!! 
  final emailId = (event as UserGetEvent).emailId;
  try {
    final user = await UserService.getUser(emailId);
    yield UserState(user: user, users: state.users); // this state arrives first, but should be second
  } catch (e) {
    add(UserErrorEvent(error: e.toString()));
  }
  break;

Expected behavior
expect states to arrive in order provided

documentation enhancement

Most helpful comment

@felangel my question answered 馃憤 happy for this issue to be closed

All 14 comments

add actually is synchronous, but since its only job is to add an event to the sink (and doesn't await for it to finish)
you get the previous result.
I am not sure how to solve this though.

this makes "predictable state management" quite unpredictable,

Hi @venil7 馃憢
Thanks for opening an issue!

This is actually working as designed. When you add an event it gets added to the internal sink and is then processed in the order in which it was added. In your case, you added some event A which is being processed and in the middle, you added another event B. The bloc will finish executing the rest of the logic in mapEventToState for event A before it starts processing the newly added event B.

I believe what you should do is just execute the logic for the UserFetchEvent instead of adding the event within mapEventToState. Hope that helps! 馃憤

thanks for speedy reply @felangel

if I just "execute logic for the UserFetchEvent" - i would need to repeat similar logic in many branches of my mapEventToState, hence I extracted it to its own event.

At least this is how Redux Thunks works, you can dispatch other events prior to returning new state, and your state will update in that particular order - dispatched events first, "final" state last;

It seams like I need to clearly separate my thunks from reducer logic with bloc (using redux terminology here), and inside each case of mapEventToState only use .add or only use yield, but never mix the 2 - otherwise as above state changes in unpredictatble order..

No problem! You can extract the duplicate logic into a private helper. Why do you say it鈥檚 unpredictable? Events will always be processed in the order in which they were added and any newly added events are pushed onto the queue. An event is considered fully processed once mapEventToState has finished executing. Does that help clarify things?

I think it's unpredictable because .adding and yielding in particular order does not guarantee state updates in that same order, contrary to other similar solutions (saga,thunk, etc)

if however this is by design, then mixing add and yield is not desirable (especially calling add before yield), and possibly needs better documenting!

I understand that coming from redux it's not what you expected but I would argue it's still predictable. You just have to understand that events are processed asynchronously in the order in which they were added.

I still question why you would mix add and yield within mapEventToState unless you're adding asynchronously (in response to a subscription). Instead, I think it's simpler to just process and yield directly. When the incoming event is UserGetEvent why do you also need a UserFetchEvent? I think it'd be much simpler to have a single UserGetEvent which results in a single UserState with the appropriate user information.

Something like:

case UserGetEvent:
  final emailId = event.emailId;
  try {
    final user = await UserService.getUser(emailId);
    yield UserState(user: user, users: state.users);
  } catch (e) {
    yield UserErrorState(error: e.toString());
  }

I will be sure to update the documentation to explicitly explain the control-flow since I agree it's not well documented anywhere 馃憤

Thoughts?

I mix add and yield because when something is being added I know theres a whole case statement dedicated to that particular event - thus promoting DRY approach. if however I need to yield new state, often i need to reintroduce pieces of old state into newly yielded state, so that i dont loose user for example (singular) while adding users (plural), etc. I understand that it can be mitigated with private helpers.

You just have to understand that events are processed asynchronously in the order in which they were added.

So .add is asynchronous after all, contrary to @bigworld12's first reply in this thread - and that clarifies a lot of things

@venil7 yeah .add is asynchronous 馃憤 I'll update the docs later today and please let me know if you have any other concerns/comments! Thanks for bringing this up 馃槃

@felangel my question answered 馃憤 happy for this issue to be closed

@venil7 awesome! I'll keep it open until I update the documentation 馃憤

Updated the documentation (https://github.com/felangel/bloc/commit/3f14929cc489c4439b1fe7b1ff23755a492157f0) 馃憤

@venil7
actually add IS synchronous :

https://github.com/felangel/bloc/blob/3f14929cc489c4439b1fe7b1ff23755a492157f0/packages/bloc/lib/src/bloc.dart#L65-L74

it doesn't use async syntax, it just INSTANTLY and Synchronously inputs states in a queue (by that i mean it blocks the current thread execution until adding the event is done), then it notifies the listener to read from that queue and the listener reads them Asynchronously (check StreamQueue.next ).

since add doesn't wait for the listener to listen, and blocks execution (doesn't return a future), it's Synchronous.

@bigworld12 thanks for clarification. Just to clarify, while add itself synchronously enqueues the event, the enqueued event is asynchronously processed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Reidond picture Reidond  路  3Comments

frankrod picture frankrod  路  3Comments

krusek picture krusek  路  3Comments

hivesey picture hivesey  路  3Comments

1AlexFix1 picture 1AlexFix1  路  3Comments