better_future 2.0.3 copy "better_future: ^2.0.3" to clipboard
better_future: ^2.0.3 copied to clipboard

Advanced asynchronous orchestration with named results, and automatic dependency management and cleanup.

BetterFuture ๐Ÿš€ #

BetterFuture is a powerful Dart library designed to orchestrate complex asynchronous workflows with ease. It goes beyond Future.wait by providing named results, automatic dependency management, and natural syntax for inter-computation communication.

Key Features โœจ #

  • ๐Ÿ“ฆ Parallel Execution: Run multiple asynchronous computations concurrently.
  • โšก FutureOr Support: Mix synchronous values and asynchronous futures seamlessly.
  • ๐Ÿท๏ธ Named Results: Access results by keys instead of positional indices.
  • ๐Ÿ”— Dependency Injection: Use the result of one computation in another within the same block.
  • ๐Ÿช„ Dynamic Syntax: Access results elegantly using $.key or $.key<T>().
  • ๐Ÿ›ก๏ธ Type Safety: Explicit casting support with built-in primitives and custom type registration.
  • ๐Ÿงน Automatic Cleanup: Clean up resources if a parallel task fails.
  • ๐ŸŒ Web & WASM Ready: Fully compatible with JS and WASM compilation targets.
  • โฉ Error Orchestration: Choose between eager or lazy failure.

Installation ๐Ÿ“ฅ #

Add better_future to your pubspec.yaml:

dependencies:
  better_future: ^2.0.0

Quick Start ๐Ÿš€ #

import 'package:better_future/better_future.dart';

void main() async {
  final results = await BetterFuture.wait({
    // Supports both synchronous values and asynchronous futures (FutureOr)
    'user_id': () => 42,
    
    // A computation depending on 'user_id'
    'profile': ($) async {
      // Notice the dynamic type-safe access using $.key<T>()
      final id = await $.user_id<int>(); 
      return fetchUserProfile(id);
    },
    
    // Another computation running in parallel
    'settings': ($) => fetchSettings(),
  });

  print(results['profile']);
  print(results['settings']);
}

Value Destructuring ๐Ÿงฉ #

Since BetterFuture.wait returns a standard Dart Map, you can use Dart 3's powerful map patterns to destructure results immediately:

final {
  'profile': UserProfile profile, 
  'settings': AppSettings settings,
} = await BetterFuture.wait<dynamic>({
  'profile': ($) => fetchProfile(),
  'settings': ($) => fetchSettings(),
});

// Now use profile and settings directly!

Advanced Usage ๐Ÿ› ๏ธ #

Dependency Management with $ #

The BetterResults object (conventionally named $) allows you to await other computations by their keys. If a computation hasn't finished yet, it will be awaited automatically.

'c': ($) async {
  final a = await $.a; // Getter syntax (returns dynamic)
  final b = await $.b<double>(); // Method syntax with casting
  return a + b;
}

Pro Tip ๐Ÿ’ก: If your keys are numeric (e.g., '1', '2'), you can access them using the $ prefix: await $.$1.

Automatic Cleanup #

When performing operations that require cleanup (like opening a file or a database transaction), BetterFuture ensures that if any task fails, the successful ones are cleaned up properly.

final results = await BetterFuture.wait<Resource>(
  {
    'res1': ($) => openResource(1),
    'res2': ($) => throw Exception('Oops!'),
  },
  cleanUp: (res) => res.dispose(), // Called for 'res1' when 'res2' fails
);

Registering Custom Types #

To use $.key<MyCustomType>(), you need to register the type first:

BetterFuture.registerType<User>();

// Later...
final user = await $.current_user<User>();

Error Handling #

  • Lazy (default): Wait for all computations to finish (or fail) before throwing the first encountered error.
  • Eager: Set eagerError: true to fail immediately as soon as any computation throws an error.

Getting Detailed Outcomes #

BetterFuture also provides BetterFuture.settle<T>(computations).

This method returns a Map<String, BetterOutcome<T>> that holds the final outcome of each computation. Concrete instances of BetterOutcome<T> can only be:

  • a BetterSuccess<T> instance (successful computation); or
  • a BetterFailure<T> instance (failed computation).

BetterFuture.settle<T>() will never complete with an error. Instead, it completes successfully once all computations have completed. Note that if any computation never completes, BetterFuture.settle<T>() will also never complete.

Also note that BetterFuture.settle<T>() does not handle cleanup on errors. When applicable, you must implement proper cleanup to ensure that resources are released.

Best Practices & Considerations โš ๏ธ #

๐Ÿ”„ Avoid Cyclic Dependencies #

Ensure your computations do not have circular dependencies. If computation A awaits B, and B awaits A, the orchestration will deadlock and never complete. Always design your workflows as a Directed Acyclic Graph (DAG).

โšก Synchronous vs Asynchronous #

BetterFuture supports both synchronous values and FutureOr functions for maximum flexibility. However:

  • Synchronous functions run immediately when BetterFuture.wait or BetterFuture.settle is called.

  • If a computation is CPU-intensive and implemented synchronously, it will block the event loop, potentially causing UI jank.

  • Recommendation: Use synchronous functions only for lightweight constants or simple state access. For heavy processing, always offload the work to an async function.

๐Ÿ—œ๏ธ Bundle Size & Performance #

BetterResults relies on Dart's dynamic method and accessor calls via the dynamic type and noSuchMethod(). This enables constructs such as:

'b': (/* dynamic */ $) async {
  final a = await $.a<int>();
  //...
}

However, this dynamic mechanism prevents the compiler from performing full tree-shaking, as some calls can only be resolved at runtime. This increases bundle size (due to additional retained code) and impacts performance (due to additional runtime resolution steps).

If size or performance are important to you, you can eliminate dynamic calls by explicitly typing $ as BetterResults. In that case, the code becomes slightly more verbose:

'b': (BetterResults $) async {
  final a = await $.get<int>('a');
  //...
}

Why BetterFuture? ๐Ÿค” #

While standard Future.wait is useful for simple parallelization, it falls short in complex real-world scenarios:

  • Orchestration, Simplified: If task B depends on task A, you often have to split your Future.wait into multiple stages or nest await calls, which can accidentally serialize tasks that could have run in parallel. BetterFuture turns your computations into a self-organizing dependency graph.

  • No More Positional Fragility: Future.wait returns a List. If you add a new future to the middle of the list, every index after it changes. BetterFuture uses named keys, making your code robust and easy to refactor.

  • Maximum Flexibility: Handle mixed workloads with ease. Mix synchronous values and asynchronous tasks, manage heterogeneous result types in a single map, and leverage Dart 3 Map patterns for clean, declarative destructuring. The library normalizes synchronization automatically.

  • Unified Reliability: Managing resource cleanup when one task in a group fails is difficult. BetterFuture handles the "rollback" logic for you via the cleanUp hook.

Examples ๐Ÿ“š #

Check out the example/ folder for focused demonstrations:

  • main.dart: Simple quick start.
  • orchestration.dart: Complex dependency graphs and timing.
  • cleanup.dart: Resource management and error recovery.
  • destructuring.dart: Dart 3 Map pattern usage.
  • settle.dart: Working with outcomes.

Inspiration ๐Ÿ’ก #

This package is a complete rewrite and evolution of the better-all TypeScript package. It brings a reimagined approach to elegant, object-based asynchronous orchestration for the Flutter and Dart ecosystem.

Support & Sponsorship โ˜• #

If you find BetterFuture useful, consider supporting its development:

Built with โค๏ธ for better Dart development.

1
likes
160
points
236
downloads

Documentation

API reference

Publisher

verified publisherd-markey.ovh

Weekly Downloads

Advanced asynchronous orchestration with named results, and automatic dependency management and cleanup.

Repository (GitHub)
View/report issues

Funding

Consider supporting this project:

github.com

License

Apache-2.0 (license)

More

Packages that depend on better_future