observeAsync<T> function

Future<T> observeAsync<T>(
  1. Future<T> operation(), {
  2. void onSuccess(
    1. Duration elapsed,
    2. T result
    )?,
  3. void onError(
    1. Duration elapsed,
    2. Object error,
    3. StackTrace stackTrace
    )?,
})

Runs the async operation, measures its wall-clock duration, and reports the outcome through the optional hooks, then returns the result (or rethrows the operation's error after onError).

onSuccess receives the elapsed time and the result; onError receives the elapsed time, the error, and its stack trace. The error is always rethrown so the wrapper is transparent to callers.

Example:

await observeAsync(
  () => repository.load(id),
  onSuccess: (d, _) => log('load ok in ${d.inMilliseconds}ms'),
  onError: (d, e, _) => log('load failed in ${d.inMilliseconds}ms: $e'),
);

Audited: 2026-06-12 11:26 EDT

Implementation

Future<T> observeAsync<T>(
  Future<T> Function() operation, {
  void Function(Duration elapsed, T result)? onSuccess,
  void Function(Duration elapsed, Object error, StackTrace stackTrace)? onError,
}) async {
  final Stopwatch stopwatch = Stopwatch()..start();
  try {
    final T result = await operation();
    stopwatch.stop();
    onSuccess?.call(stopwatch.elapsed, result);
    return result;
  } on Object catch (error, stackTrace) {
    // Catch-all is intentional: an observability wrapper must time and report
    // EVERY failure mode, then rethrow so it never changes the control flow.
    stopwatch.stop();
    onError?.call(stopwatch.elapsed, error, stackTrace);
    rethrow;
  }
}