@@ -54,55 +54,114 @@ final class EventCompleter<Event extends StateMachineEvent,
54
54
/// here and chain it with later stack traces.
55
55
final StackTrace stackTrace;
56
56
57
- /// The zone in which this event was created.
57
+ /// The zone in which this event was created, used to guarantee Zone values
58
+ /// are present from the Zone in which the event was created.
58
59
///
59
- /// Used in [run] to guarantee callbacks run in the same zone that this event
60
+ /// Due to how Zones work with Streams, it cannot be guaranteed that the Zone
61
+ /// in which this event is accepted (which is the zone in which the state
62
+ /// machine was created) will be the same as the zone in which the event
60
63
/// was created.
61
64
final Zone _zone = Zone .current;
62
- final Completer <void > _acceptedCompleter = Completer ();
63
- final Completer <State > _completer = Completer ();
65
+
66
+ /// Every time [accepted] or [completed] is called, generate a new completer.
67
+ /// This is because the Zone in which a [Completer] is instantiated **must**
68
+ /// match the Zone in which its future is listened to, otherwise the future
69
+ /// will never complete.
70
+ ///
71
+ /// That is, running `_zone.run(completer.complete)` would still throw
72
+ /// the error in the Zone where the completer was instantiated. And due
73
+ /// to how Zone's work, a listener for a completer which completes in a
74
+ /// different error zone will never finish.
75
+ ///
76
+ /// The following example illustrates the problem we're trying to solve
77
+ /// here:
78
+ ///
79
+ /// ```dart
80
+ /// import "dart:async";
81
+ ///
82
+ /// void main() {
83
+ /// final completer = Completer();
84
+ /// runZonedGuarded(() async {
85
+ /// await completer.future;
86
+ /// print('never printed');
87
+ /// }, (e, s) {
88
+ /// print('never printed');
89
+ /// });
90
+ /// completer.future.catchError((e) => print('outer zone: $e'));
91
+ /// completer.completeError('error');
92
+ /// }
93
+ /// ```
94
+ ///
95
+ /// See this [Dart issue] (https://github.com/dart-lang/sdk/issues/49457) for
96
+ /// more information.
97
+ final List <Completer <void >> _acceptedCompleters = [];
98
+ final List <Completer <State >> _completers = [];
99
+
100
+ var _accepted = false ;
101
+ (State ? , Object ? , StackTrace ? )? _completion;
64
102
65
103
/// Completes when the event is accepted by the respective state machine.
66
104
///
67
105
/// After this completes, intermediate changes can be listened for on the
68
106
/// event's state machine.
69
- Future <void > get accepted => _acceptedCompleter.future;
107
+ Future <void > get accepted {
108
+ if (_accepted) {
109
+ return Future .value ();
110
+ }
111
+ final completer = Completer <void >();
112
+ _acceptedCompleters.add (completer);
113
+ return completer.future;
114
+ }
70
115
71
116
/// Completes with the stopping state emitted after the full propagation
72
117
/// of this event.
73
- Future <State > get completed => _completer.future;
118
+ Future <State > get completed {
119
+ if (_completion case (final completion? , _, _)) {
120
+ return Future .value (completion);
121
+ }
122
+ if (_completion case (_, final error? , final stackTrace? )) {
123
+ return Future .error (error, stackTrace);
124
+ }
125
+ final completer = Completer <State >();
126
+ _completers.add (completer);
127
+ return completer.future;
128
+ }
74
129
75
130
/// Accepts the event by a state machine.
76
131
void accept () {
77
- if (! _acceptedCompleter.isCompleted) {
78
- _acceptedCompleter.complete ();
132
+ _accepted = true ;
133
+ for (final completer in _acceptedCompleters) {
134
+ if (! completer.isCompleted) {
135
+ completer.complete ();
136
+ }
79
137
}
138
+ _acceptedCompleters.clear ();
80
139
}
81
140
82
141
/// Completes the event propagation with its stopping state.
83
142
void complete (State state) {
84
- if (! _completer.isCompleted) {
85
- _completer.complete (state);
143
+ _completion ?? = (state, null , null );
144
+ for (final completer in _completers) {
145
+ if (! completer.isCompleted) {
146
+ completer.complete (state);
147
+ }
86
148
}
149
+ _completers.clear ();
87
150
}
88
151
89
152
/// Completes the event propagation with an error, if the event failed to
90
153
/// resolve to a meaningful stopping state.
91
154
void completeError (Object error, StackTrace stackTrace) {
92
- if (! _completer.isCompleted) {
93
- _completer.completeError (error, stackTrace);
155
+ _completion ?? = (null , error, stackTrace);
156
+ for (final completer in _completers) {
157
+ if (! completer.isCompleted) {
158
+ completer.completeError (error, stackTrace);
159
+ }
94
160
}
161
+ _completers.clear ();
95
162
}
96
163
97
164
/// Runs [body] in the [Zone] which this event was created.
98
- ///
99
- /// Due to how Zones work in Flutter, it cannot be guaranteed that the Zone
100
- /// in which this event is accepted (which is the zone in which the state
101
- /// machine was created) will be the same as the zone in which the _event_
102
- /// was created.
103
- ///
104
- /// Since events are created in the same zone as the user's call, we should
105
- /// default to using this zone for running state machine actions.
106
165
R run <R >(R Function () body) => _zone.run (body);
107
166
108
167
/// Ignores the result of the event completer.
0 commit comments