run<R> method

FutureOr<R> run<R>(
  1. FutureOr<R> callback()
)
inherited

Runs the given callback in the zone and returns the result of that call.

Exceptions will be forwarded to the exception handler and rethrown.

Implementation

FutureOr<R> run<R>(FutureOr<R> Function() callback) {
  // Run the users callback, and handle uncaught exceptions.
  //
  // **NOTE**: It might be tempting to try and optimize this, but this is
  // required otherwise tests timeout - the completer needs to be created
  // outside as Dart swallows rejected futures outside the 'onError: '
  // callback for Future.
  final completer = Completer<R>();
  FutureOr<R>? result;
  runInZone(() {
    try {
      result = callback();
      if (result is Future<Object>) {
        final resultCast = unsafeCast<Future<R>>(result);
        resultCast.then((result) {
          completer.complete(result);
        }, onError: (e, s) {
          final sCasted = unsafeCast<StackTrace>(s);
          final eCasted = unsafeCast<Object>(e);
          completer.completeError(eCasted, sCasted);
          handleUncaughtException(eCasted, sCasted);
        });
      }
    } catch (e, s) {
      handleUncaughtException(e, s);
      // Note, due to the rethrow a synchronous error thrown in callback will
      // cause the `run` function never to return. This is important to note
      // because of the handling logic below.
      rethrow;
    }
  });
  // Some complexity in null-safety: `FutureOr<R>` can mean:
  // 1. Future<R> where R might be nullable or non-nullable.
  // 2. R, where R is non-nullable.
  // 3. R, where R is nullable.
  //
  // We need to carefully evaluate the three different cases to avoid a subtle
  // breaking API change (we still want our users to be allowed to return null
  // if they expect a nullable value) - i.e.:
  //
  //   // Should continue to be valid.
  //   String? name = run(() => null);
  //
  // See also: https://dart.dev/null-safety/understanding-null-safety#nullability-and-generics.
  final r = result;
  if (r == null) {
    // The variable r can only be null here if the callback returned null,
    // which in turn can only happen if either we were called from opted-in
    // code and R is nullable, or if we were called from opted-out code.
    //
    // In either case, the case here should always suceed. By explicitly
    // checking for null, we avoid having a cast in the final (and likely more
    // frequently executed) branch of the conditional.
    return r as R;
  } else if (r is Future<Object>) {
    // Return as a Future<R>.
    return completer.future;
  } else {
    // Return as R.
    return r;
  }
}