wait<T> 静态方法

Future<List<T>> wait<T>(
  1. Iterable<Future<T>> futures,
  2. {bool eagerError = false,
  3. void cleanUp(
    1. T successValue
    )?}
)

等待多个 Future 完成,并收集它们的结果。

返回一个 Future,一旦所有提供的 Future 完成后,它将完成,无论是通过结果还是通过错误(如果提供的任何一个 Future 失败)。

返回 Future 的值将是一个列表,其中包含按照提供的 Future 的顺序迭代 futures 产生的所有值。

如果任何一个 Future 以错误完成,则返回的 Future 也以那个错误完成。如果后续的 Future 也以错误完成,则这些错误将被丢弃。

如果 eagerError 为 true,则一旦其中一个 Future 出错,返回的 Future 立即以错误完成。否则,必须在返回的 Future 完成之前,所有 Future 都必须完成(仍然是以第一个错误;剩余的错误将被静默丢弃)。

如果在错误的情况下,提供了 cleanUp,则会在任何成功的 Future 的非空结果上调用 cleanUp。这使得可以清理那些否则会丢失的资源(因为返回的 Future 不提供对这些值的访问)。如果没有错误,cleanUp 函数不会被使用。

调用 cleanUp 不应该抛出异常。如果它抛出了异常,那么它将是一个未捕获的异步错误。

示例

void main() async {
  var value = await Future.wait([delayedNumber(), delayedString()]);
  print(value); // [2, result]
}

Future<int> delayedNumber() async {
  await Future.delayed(const Duration(seconds: 2));
  return 2;
}

Future<String> delayedString() async {
  await Future.delayed(const Duration(seconds: 2));
  return 'result';
}

实现

static Future<List<T>> wait<T>(Iterable<Future<T>> futures,
    {bool eagerError = false, void cleanUp(T successValue)?}) {
  @pragma('vm:awaiter-link')
  final _Future<List<T>> _future = _Future<List<T>>();
  List<T?>? values; // Collects the values. Set to null on error.
  int remaining = 0; // How many futures are we waiting for.
  Object? error; // The first error from a future.
  StackTrace? stackTrace; // The stackTrace that came with the error.

  // Handle an error from any of the futures.
  void handleError(Object theError, StackTrace theStackTrace) {
    var remainingResults = --remaining;
    List<T?>? valueList = values;
    if (valueList != null) {
      // First error, set state to represent error having already happened.
      values = null;
      error = theError;
      stackTrace = theStackTrace;
      // Then clean up any already successfully produced results.
      if (cleanUp != null) {
        for (var value in valueList) {
          if (value != null) {
            // Ensure errors from `cleanUp` are uncaught.
            T cleanUpValue = value;
            Future.sync(() {
              cleanUp(cleanUpValue);
            });
          }
        }
      }
      if (remainingResults == 0 || eagerError) {
        _future._completeError(theError, theStackTrace);
      }
    } else {
      // Not the first error.
      if (remainingResults == 0 && !eagerError) {
        // Last future completed, non-eagerly report the first error.
        _future._completeError(error!, stackTrace!);
      }
    }
  }

  try {
    // As each future completes, put its value into the corresponding
    // position in the list of values.
    for (var future in futures) {
      int pos = remaining;
      future.then((T value) {
        var remainingResults = --remaining;
        List<T?>? valueList = values;
        if (valueList != null) {
          // No errors yet.
          assert(valueList[pos] == null);
          valueList[pos] = value;
          if (remainingResults == 0) {
            _future._completeWithValue(
                [for (var value in valueList) value as T]);
          }
        } else {
          // Prior error, clean-up this value if necessary.
          if (cleanUp != null && value != null) {
            // Ensure errors from cleanUp are uncaught.
            Future.sync(() {
              cleanUp(value);
            });
          }
          if (remainingResults == 0 && !eagerError) {
            // Last future completed, non-eagerly report the first error.
            _future._completeError(error!, stackTrace!);
          }
        }
      }, onError: handleError);
      // Increment the 'remaining' after the call to 'then'.
      // If that call throws, we don't expect any future callback from
      // the future, and we also don't increment remaining.
      remaining++;
    }
    if (remaining == 0) {
      // No elements in iterable.
      return _future.._completeWithValue(<T>[]);
    }
    values = List<T?>.filled(remaining, null);
  } catch (e, st) {
    // The error must have been thrown while iterating over the futures
    // list, or while installing a callback handler on the future.
    // This is a breach of the `Future` protocol, but we try to handle it
    // gracefully.
    if (remaining == 0 || eagerError) {
      // Throw a new Future.error.
      // Don't just call `_future._completeError` since that would propagate
      // the error too eagerly, not giving the callers time to install
      // error handlers.
      // Also, don't use `_asyncCompleteError` since that one doesn't give
      // zones the chance to intercept the error.
      return new Future.error(e, st);
    } else {
      // Don't allocate a list for values, thus indicating that there was an
      // error.
      // Set error to the caught exception.
      error = e;
      stackTrace = st;
    }
  }
  return _future;
}