guard<T> method

Future<T> guard <T>(Future<T> body())

Calls the given callback in a new async scope. The callback argument is the asynchronous body of the calling method. The calling method is said to be "guarded". Nested calls to guarded methods from within the body of this one are fine, but calls to other guarded methods from outside the body of this one before this one has finished will throw an exception.

This method first calls guardSync.

Implementation

static Future<T> guard<T>(Future<T> body()) {
  guardSync();
  final Zone zone = Zone.current.fork(
    zoneValues: <dynamic, dynamic>{
      _scopeStack: true // so we can recognize this as our own zone
    }
  );
  final _AsyncScope scope = _AsyncScope(StackTrace.current, zone);
  _scopeStack.add(scope);
  final Future<T> result = scope.zone.run<Future<T>>(body);
  T resultValue; // This is set when the body of work completes with a result value.
  Future<T> completionHandler(dynamic error, StackTrace stack) {
    assert(_scopeStack.isNotEmpty);
    assert(_scopeStack.contains(scope));
    bool leaked = false;
    _AsyncScope closedScope;
    final StringBuffer message = StringBuffer();
    while (_scopeStack.isNotEmpty) {
      closedScope = _scopeStack.removeLast();
      if (closedScope == scope)
        break;
      if (!leaked) {
        message.writeln('Asynchronous call to guarded function leaked. You must use "await" with all Future-returning test APIs.');
        leaked = true;
      }
      final _StackEntry originalGuarder = _findResponsibleMethod(closedScope.creationStack, 'guard', message);
      if (originalGuarder != null) {
        message.writeln(
          'The test API method "${originalGuarder.methodName}" '
          'from class ${originalGuarder.className} '
          'was called from ${originalGuarder.callerFile} '
          'on line ${originalGuarder.callerLine}, '
          'but never completed before its parent scope closed.'
        );
      }
    }
    if (leaked) {
      if (error != null) {
        message.writeln('An uncaught exception may have caused the guarded function leak. The exception was:');
        message.writeln('$error');
        message.writeln('The stack trace associated with this exception was:');
        FlutterError.defaultStackFilter(stack.toString().trimRight().split('\n')).forEach(message.writeln);
      }
      throw FlutterError(message.toString().trimRight());
    }
    if (error != null)
      return Future<T>.error(error, stack);
    return Future<T>.value(resultValue);
  }
  return result.then<T>(
    (T value) {
      resultValue = value;
      return completionHandler(null, null);
    },
    onError: completionHandler
  );
}