Sync<T extends Object> constructor

Sync<T extends Object>(
  1. @mustBeAnonymous @noFutures T noFutures(), {
  2. @noFutures TOnErrorCallback<T>? onError,
  3. @noFutures TVoidCallback? onFinalize,
})

Creates a Sync executing a synchronous function noFutures.

IMPORTANT:

Do not use any Futures in noFutures to ensure errors are be caught and propagated.

Implementation

factory Sync(
  @mustBeAnonymous @noFutures T Function() noFutures, {
  @noFutures TOnErrorCallback<T>? onError,
  @noFutures TVoidCallback? onFinalize,
}) {
  assert(
    isSubtype<T, Never>() || !isSubtype<T, Future<Object>>(),
    '$T must never be a Future.',
  );
  Result<T> result;
  try {
    result = Ok(noFutures());
  } on Err catch (err) {
    result = err.transfErr<T>();
  } catch (error, stackTrace) {
    if (onError == null) {
      result = Err<T>(error, stackTrace: stackTrace);
    } else {
      try {
        result = onError(error, stackTrace);
      } on Err catch (err) {
        // `onError` itself can throw an `Err` — preserve its statusCode
        // and breadcrumbs rather than nesting it as another Err's value.
        result = err.transfErr<T>();
      } catch (error, stackTrace) {
        result = Err<T>(error, stackTrace: stackTrace);
      }
    }
  }
  // Run `onFinalize` separately so its throws are absorbed into `result`.
  // Following standard `try/finally` semantics, a thrown finalize error
  // overrides whatever `result` held — a failed cleanup is a meaningful
  // failure mode that callers need to see.
  if (onFinalize != null) {
    try {
      onFinalize();
    } on Err catch (err) {
      result = err.transfErr<T>();
    } catch (error, stackTrace) {
      result = Err<T>(error, stackTrace: stackTrace);
    }
  }
  return Sync.result(result);
}