LogoSignals.dart
Copy Markdown
rodydavis/signals.dart 999999

Type: computedAsync

API reference and details for computedAsync from signals.dart.

computedAsync#

Kind: function  |  Package: package:signals_core

Function: computedAsync#

FutureSignal<T> computedAsync(Future<T> Function() fn, {AsyncSignalOptions<T>? options, @Deprecated('Use options: AsyncSignalOptions(initialValue: ...) instead') T? initialValue, @Deprecated('Use options: AsyncSignalOptions(dependencies: ...) instead') List<ReadonlySignal<dynamic>>? dependencies, @Deprecated('Use options: AsyncSignalOptions(lazy: ...) instead') bool? lazy, @Deprecated('Use options: AsyncSignalOptions(autoDispose: ...) instead') bool? autoDispose, @Deprecated('Use options: AsyncSignalOptions(name: ...) instead') String? debugLabel})

Create an asynchronous computed signal with implicit dependency tracking.

computedAsync takes an asynchronous callback function to compute the value of the signal. Any signal read synchronously inside the callback is automatically tracked as a dependency, and the computed signal is re-evaluated when any of those dependencies change.

⚠️ The Async Gap Gotcha#

Because Dart yields execution at every await expression, the reactive context that automatically tracks reads is lost after an asynchronous gap.

Rule: All signal values MUST be read synchronously before the first await statement.

❌ Incorrect Pattern (Signal read after await is NOT tracked):

final movie = computedAsync(() async {
  await Future.delayed(Duration(milliseconds: 100));
  // BUG: movieId is read AFTER an async gap.
  // Changes to movieId will NOT re-evaluate this computedAsync!
  return fetchMovie(movieId.value);
});

Correct Pattern (Read dependencies synchronously first):

final movie = computedAsync(() async {
  // Capture all dependency values synchronously at the start!
  final currentId = movieId.value;

  await Future.delayed(Duration(milliseconds: 100));
  // Use the captured local variable after the async gap
  return fetchMovie(currentId);
});

Advanced Example: Search Query with Debouncing#

final searchQuery = signal('');

final searchResults = computedAsync(() async {
  // Capture dependency synchronously
  final query = searchQuery.value;

  if (query.isEmpty) return <SearchResult>[];

  // Debounce: Wait 300ms before making the API request
  await Future.delayed(Duration(milliseconds: 300));

  return performSearchApiCall(query);
});

References#

The computedAsync type is referenced and used in the following pages: