|
1 | 1 | import 'dart:async'; |
2 | 2 |
|
3 | | -import 'package:meta/meta.dart'; |
4 | | - |
5 | 3 | import '../core/signals.dart'; |
6 | 4 | import '../mixins/event_sink.dart'; |
7 | 5 | import 'state.dart'; |
@@ -173,60 +171,77 @@ class AsyncSignal<T> extends Signal<AsyncState<T>> |
173 | 171 | super.value, { |
174 | 172 | super.debugLabel, |
175 | 173 | super.autoDispose, |
176 | | - }) : _initialValue = value; |
| 174 | + }) : _initialValue = value, |
| 175 | + _completion = _AsyncCompletionSignal<T>( |
| 176 | + initialValue: value, |
| 177 | + debugLabel: debugLabel != null ? '${debugLabel}_completion' : null, |
| 178 | + ); |
| 179 | + |
| 180 | + @override |
| 181 | + void dispose() { |
| 182 | + _completion.dispose(); |
| 183 | + super.dispose(); |
| 184 | + } |
177 | 185 |
|
178 | 186 | final AsyncState<T> _initialValue; |
179 | 187 | bool _initialized = false; |
180 | 188 |
|
181 | | - /// Internal Completer for values |
182 | | - @internal |
183 | | - Completer<bool> completer = Completer<bool>(); |
| 189 | + final _AsyncCompletionSignal<T> _completion; |
| 190 | + |
| 191 | + /// Tracks the async completion of this signal. |
| 192 | + /// |
| 193 | + /// Notifies if the value of this signal changes to [AsyncData] or [AsyncError], |
| 194 | + /// but not if it changes to [AsyncLoading]. |
| 195 | + /// |
| 196 | + /// Intended to be used for tracking dependencies across async gaps, |
| 197 | + /// when awaiting the [future] of this signal. |
| 198 | + /// |
| 199 | + /// ```dart |
| 200 | + /// computedFrom([someAsyncSignal.completion], (_) async { |
| 201 | + /// await Future.delayed(const Duration(seconds: 1)); |
| 202 | + /// return await someAsyncSignal.future; |
| 203 | + /// }); |
| 204 | + /// ``` |
| 205 | + ReadonlySignal<Future<T>> get completion => _completion; |
184 | 206 |
|
185 | 207 | /// The future of the signal completer |
186 | | - Future<T> get future async { |
187 | | - value; |
188 | | - await completer.future; |
189 | | - return value.requireValue; |
190 | | - } |
| 208 | + Future<T> get future => completion.value; |
191 | 209 |
|
192 | 210 | /// Returns true if the signal is completed an error or data |
193 | 211 | bool get isCompleted { |
194 | | - value; |
195 | | - return completer.isCompleted; |
| 212 | + future; |
| 213 | + return _completion.isCompleted; |
| 214 | + } |
| 215 | + |
| 216 | + @override |
| 217 | + bool set(AsyncState<T> val, {bool force = false}) { |
| 218 | + return batch(() { |
| 219 | + _completion.setValue(val); |
| 220 | + return super.set(val, force: force); |
| 221 | + }); |
196 | 222 | } |
197 | 223 |
|
198 | 224 | /// Set the error with optional stackTrace to [AsyncError] |
199 | 225 | void setError(Object error, [StackTrace? stackTrace]) { |
200 | | - batch(() { |
201 | | - value = AsyncState.error(error, stackTrace); |
202 | | - if (completer.isCompleted) completer = Completer<bool>(); |
203 | | - completer.complete(true); |
204 | | - }); |
| 226 | + set(AsyncState.error(error, stackTrace)); |
205 | 227 | } |
206 | 228 |
|
207 | 229 | /// Set the value to [AsyncData] |
208 | 230 | void setValue(T value) { |
209 | | - batch(() { |
210 | | - this.value = AsyncState.data(value); |
211 | | - if (completer.isCompleted) completer = Completer<bool>(); |
212 | | - completer.complete(true); |
213 | | - }); |
| 231 | + set(AsyncState.data(value)); |
214 | 232 | } |
215 | 233 |
|
216 | 234 | /// Set the loading state to [AsyncLoading] |
217 | 235 | void setLoading([AsyncState<T>? state]) { |
218 | | - batch(() { |
219 | | - value = state ?? AsyncState.loading(); |
220 | | - completer = Completer<bool>(); |
221 | | - }); |
| 236 | + set(state ?? AsyncState.loading()); |
222 | 237 | } |
223 | 238 |
|
224 | 239 | /// Reset the signal to the initial value |
225 | 240 | void reset([AsyncState<T>? value]) { |
226 | 241 | batch(() { |
227 | | - this.value = value ?? _initialValue; |
| 242 | + super.value = value ?? _initialValue; |
228 | 243 | _initialized = false; |
229 | | - if (completer.isCompleted) completer = Completer<bool>(); |
| 244 | + _completion.setValue(const AsyncLoading()); |
230 | 245 | }); |
231 | 246 | } |
232 | 247 |
|
@@ -435,3 +450,69 @@ AsyncSignal<T> asyncSignal<T>( |
435 | 450 | autoDispose: autoDispose, |
436 | 451 | ); |
437 | 452 | } |
| 453 | + |
| 454 | +class _AsyncCompletionSignal<T> extends Signal<Future<T>> { |
| 455 | + _AsyncCompletionSignal._( |
| 456 | + Completer<T> completer, { |
| 457 | + required super.debugLabel, |
| 458 | + }) : _completer = completer, |
| 459 | + super(_ignoreUncaughtErrors(completer.future)); |
| 460 | + |
| 461 | + factory _AsyncCompletionSignal({ |
| 462 | + required AsyncState<T> initialValue, |
| 463 | + required String? debugLabel, |
| 464 | + }) { |
| 465 | + final completer = Completer<T>(); |
| 466 | + |
| 467 | + switch (initialValue) { |
| 468 | + case AsyncLoading(): |
| 469 | + break; |
| 470 | + |
| 471 | + case AsyncData(:final value): |
| 472 | + completer.complete(value); |
| 473 | + break; |
| 474 | + |
| 475 | + case AsyncError(:final error, :final stackTrace): |
| 476 | + completer.completeError(error, stackTrace); |
| 477 | + break; |
| 478 | + } |
| 479 | + |
| 480 | + return _AsyncCompletionSignal._(completer, debugLabel: debugLabel); |
| 481 | + } |
| 482 | + |
| 483 | + Completer<T> _completer; |
| 484 | + |
| 485 | + bool _initialized = false; |
| 486 | + bool get isCompleted => _initialized && _completer.isCompleted; |
| 487 | + |
| 488 | + void setValue(AsyncState<T> value) { |
| 489 | + if (_completer.isCompleted) { |
| 490 | + _completer = Completer<T>(); |
| 491 | + this.value = _ignoreUncaughtErrors(_completer.future); |
| 492 | + } |
| 493 | + |
| 494 | + switch (value) { |
| 495 | + case AsyncLoading(): |
| 496 | + return; |
| 497 | + |
| 498 | + case AsyncData(:final value): |
| 499 | + _initialized = true; |
| 500 | + _completer.complete(value); |
| 501 | + return; |
| 502 | + |
| 503 | + case AsyncError(:final error, :final stackTrace): |
| 504 | + _initialized = true; |
| 505 | + _completer.completeError(error, stackTrace); |
| 506 | + return; |
| 507 | + } |
| 508 | + } |
| 509 | + |
| 510 | + static Future<T> _ignoreUncaughtErrors<T>(Future<T> future) { |
| 511 | + // Makes sure the future never reports an uncaught error to |
| 512 | + // the current zone. Seems to be necessary to avoid uncaught |
| 513 | + // errors to be reported when async signals are being used |
| 514 | + // synchronously, i.e. when their .future is not used. |
| 515 | + future.ignore(); |
| 516 | + return future; |
| 517 | + } |
| 518 | +} |
0 commit comments