run<R> method
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;
}
}