Best way to use effect
to work around async computed
?
#52
-
I have a class TransactionsStore {
TransactionsStore() {
_registerEffects();
}
void _registerEffects() {
_spendByCategoryEffect();
...
}
void _spendByCategoryEffect() {
effect(() {
// reads 1 or more signal values and performs operations to get a result
_spendByCategory.value = result;
});
}
} I follow this same pattern for a bunch of other pointers, but what I've noticed is that these can run out of order, which means previous invocations (when not all signals were in a proper state) can overwrite future invocations, leading to invalid/incorrect state. One such example is: void _spendByCategoryEffect() {
effect(() async {
final run = Random().nextInt(10000); // Get a unique-ish run id
final expenses = _expensesInDateRangeAndView.value;
print('_spendByCategoryEffect => ${expenses.values.flattened.length} in run $run');
final out = <Category, int>{};
for (final entry in expenses.entries) {
final sum = await _messengers.sumAmountUnique.send(
(entry.value, expenseFilter),
);
out[entry.key] = sum;
}
print('_spendByCategoryEffect => returning ${out.values.sum} in run $run');
untracked(() => _spendByCategory.value = out);
});
} This logs the following: flutter: _spendByCategoryEffect => 0 in run 3419
flutter: _spendByCategoryEffect => returning 0 in run 3419
flutter: _spendByCategoryEffect => 0 in run 4969
flutter: _spendByCategoryEffect => returning 0 in run 4969
flutter: _spendByCategoryEffect => 0 in run 3757
flutter: _spendByCategoryEffect => returning 0 in run 3757
flutter: _spendByCategoryEffect => 0 in run 8062
flutter: _spendByCategoryEffect => returning 0 in run 8062
flutter: _spendByCategoryEffect => 0 in run 1803 <-- Things get funky here, see last log
flutter: _spendByCategoryEffect => 0 in run 3586
flutter: _spendByCategoryEffect => 0 in run 315
flutter: _spendByCategoryEffect => 0 in run 5708
flutter: _spendByCategoryEffect => 192 in run 2517
flutter: _spendByCategoryEffect => returning 0 in run 3586
flutter: _spendByCategoryEffect => returning 0 in run 315
flutter: _spendByCategoryEffect => returning 0 in run 5708
flutter: _spendByCategoryEffect => returning -12842740 in run 2517 <-- The valid data I want
flutter: _spendByCategoryEffect => returning 0 in run 1803 <-- This is from a previous run Is there any way to guard against this, or is there a better way to achieve a non- |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments
-
FWIW, I experimented with doing the thing I originally said I didn't want to do (make my fields flutter: _spendByCategoryEffect => 0 in run 2145
flutter: _spendByCategoryEffect => 0 in run 157
flutter: _spendByCategoryEffect => 192 in run 7495
flutter: _spendByCategoryEffect => returning 0 in run 2145
flutter: _spendByCategoryEffect => returning 0 in run 157
flutter: _spendByCategoryEffect => returning -12842740 in run 7495 So while the beginning and end of execution is separated (I believe due to the async gap in the Still wondering though if this is the best way. |
Beta Was this translation helpful? Give feedback.
-
I am looking into supporting the flag that allows for signal writes in an effect like angular does and I think that would help here: https://stackoverflow.com/questions/76571331/using-async-await-in-angular-computed-signal I am also planning on building a signals_lint package to help with some of these cases |
Beta Was this translation helpful? Give feedback.
-
In 1.5.0 introduced import 'package:signals/signals.dart';
void main() {
Future<String> fetch(int id) async {
await Future.delayed(const Duration(milliseconds: 5));
return '$id';
}
Stream<int> idChanges() async* {
yield 1;
await Future.delayed(const Duration(milliseconds: 5));
yield 2;
await Future.delayed(const Duration(milliseconds: 5));
yield 3;
}
final id = asyncSignalFromStream(idChanges, initialValue: 0);
final user = fetch(id.value).toSignalWithDefault('guest');
final greeting = computed(() => 'Hello, ${user.value}');
effect(() {
print('current id: $id');
user.resetFuture(() => fetch(id.value));
});
effect(() {
print('greeting: $greeting');
});
} This signal works a lot more like any other signal and can be reset based on a dependency changing (like computed). In the example there is an effect just for updating the computed value from an async signal |
Beta Was this translation helpful? Give feedback.
-
I've been toying with this for hours, and I think it's still really tricky/verbose. As I see it, each and every computed value that depends on an async signal requires being reset if a dependency changes. What if each computed value has 3 dependencies, and you have 10 computed values? The most ergonomic thing to me would be an |
Beta Was this translation helpful? Give feedback.
-
I thought it'd be helpful to put together a gist of what I'm up against, here's the class that does the bulk of computation in the app, which is now running fully sync due to my inability to figure out how to make async work without race conditions: https://gist.github.com/btrautmann/a3153677c73f72f3c458cadff17a4cae The |
Beta Was this translation helpful? Give feedback.
In 1.5.0 introduced
AsyncSignal
which should make async computed a lot easier to achieve: