withDelay method

Future<T> withDelay(
  1. Duration minOperationTime, {
  2. Duration threshold = const Duration(milliseconds: 50),
})

If the time it takes for this to complete is less than minOperationTime, then a Future.delayed is awaited for the remaining time. The delay also affects any errors of this Future.

The Future returned by this method will take at least as much time as specified by minOperationTime to complete.

Implementation

Future<T> withDelay(
  Duration minOperationTime, {
  Duration threshold = const Duration(milliseconds: 50),
}) async {
  if (minOperationTime.isNegative) {
    throw ArgumentError.value(
      minOperationTime,
      "minOperationTime",
      "Must be a positive duration",
    );
  }

  if (threshold.isNegative) {
    throw ArgumentError.value(
      threshold,
      "threshold",
      "Must be a positive duration",
    );
  }

  if (minOperationTime < threshold) {
    throw ArgumentError.value(
      minOperationTime,
      "minOperationTime",
      "The minimum operation time cannot be less than the threshold time",
    );
  }

  Result<T> res;

  final watch = clock.stopwatch();
  watch.start();
  try {
    res = Result.value(await this);
    // Forward error objects of any type.
    // ignore: avoid_catches_without_on_clauses
  } catch (err, st) {
    res = Result.error(err, st);
  } finally {
    watch.stop();
  }

  final delta = minOperationTime - watch.elapsed;

  // Only wait if we have at least threshold.
  if (delta >= threshold) {
    // TODO(obemu): Maybe find a better fix for the following hack.
    // If the operation completed too fast then we wait for
    // [minOperationTime], because waiting for [delta]
    // in this case may not work (depends on the platform and
    // compilation type) so that [withDelay] actually causes a delay of at
    // least [minOperationTime].
    final delay = watch.elapsed < const Duration(milliseconds: 100)
        ? minOperationTime
        : delta;

    await Future.delayed(delay);
  }

  if (res.asError case final ErrorResult err) {
    throw Error.throwWithStackTrace(err.error, err.stackTrace);
  }

  return res.asValue!.value;
}