Skip to content

Completer<T> abstract interface

abstract interface class Completer<T>

Annotations: @vmIsolateUnsendable

A way to produce Future objects and to complete them later with a value or error.

Most of the time, the simplest way to create a future is to just use one of the Future constructors to capture the result of a single asynchronous computation:

dart
Future(() { doSomething(); return result; });

or, if the future represents the result of a sequence of asynchronous computations, they can be chained using Future.then or similar functions on Future:

dart
Future doStuff(){
  return someAsyncOperation().then((result) {
    return someOtherAsyncOperation(result);
  });
}

If you do need to create a Future from scratch — for example, when you're converting a callback-based API into a Future-based one — you can use a Completer as follows:

dart
class AsyncOperation {
  final Completer _completer = new Completer();

  Future<T> doOperation() {
    _startOperation();
    return _completer.future; // Send future object back to client.
  }

  // Something calls this when the value is ready.
  void _finishOperation(T result) {
    _completer.complete(result);
  }

  // If something goes wrong, call this.
  void _errorHappened(error) {
    _completer.completeError(error);
  }
}

Constructors

Completer() factory

factory Completer()

Creates a new completer.

The general workflow for creating a new future is to 1) create a new completer, 2) hand out its future, and, at a later point, 3) invoke either complete or completeError.

The completer completes the future asynchronously. That means that callbacks registered on the future are not called immediately when complete or completeError is called. Instead the callbacks are delayed until a later microtask.

Example:

dart
var completer = new Completer();
handOut(completer.future);
later: {
  completer.complete('completion value');
}
Implementation
dart
factory Completer() => _AsyncCompleter<T>();

Completer.sync() factory

factory Completer.sync()

Completes the future synchronously.

This constructor should be avoided unless the completion of the future is known to be the final result of another asynchronous operation. If in doubt use the default Completer constructor.

Using an normal, asynchronous, completer will never give the wrong behavior, but using a synchronous completer incorrectly can cause otherwise correct programs to break.

A synchronous completer is only intended for optimizing event propagation when one asynchronous event immediately triggers another. It should not be used unless the calls to complete and completeError are guaranteed to occur in places where it won't break Future invariants.

Completing synchronously means that the completer's future will be completed immediately when calling the complete or completeError method on a synchronous completer, which also calls any callbacks registered on that future.

Completing synchronously must not break the rule that when you add a callback on a future, that callback must not be called until the code that added the callback has completed. For that reason, a synchronous completion must only occur at the very end (in "tail position") of another synchronous event, because at that point, completing the future immediately is be equivalent to returning to the event loop and completing the future in the next microtask.

Example:

dart
var completer = Completer.sync();
// The completion is the result of the asynchronous onDone event.
// No other operation is performed after the completion. It is safe
// to use the Completer.sync constructor.
stream.listen(print, onDone: () { completer.complete("done"); });

Bad example. Do not use this code. Only for illustrative purposes:

dart
var completer = Completer.sync();
completer.future.then((_) { bar(); });
// The completion is the result of the asynchronous onDone event.
// However, there is still code executed after the completion. This
// operation is *not* safe.
stream.listen(print, onDone: () {
  completer.complete("done");
  foo();  // In this case, foo() runs after bar().
});
Implementation
dart
factory Completer.sync() => _SyncCompleter<T>();

Properties

future no setter

Future<T> get future

The future that is completed by this completer.

The future that is completed when complete or completeError is called.

Implementation
dart
Future<T> get future;

hashCode no setter inherited

int get hashCode

The hash code for this object.

A hash code is a single integer which represents the state of the object that affects operator == comparisons.

All objects have hash codes. The default hash code implemented by Object represents only the identity of the object, the same way as the default operator == implementation only considers objects equal if they are identical (see identityHashCode).

If operator == is overridden to use the object state instead, the hash code must also be changed to represent that state, otherwise the object cannot be used in hash based data structures like the default Set and Map implementations.

Hash codes must be the same for objects that are equal to each other according to operator ==. The hash code of an object should only change if the object changes in a way that affects equality. There are no further requirements for the hash codes. They need not be consistent between executions of the same program and there are no distribution guarantees.

Objects that are not equal are allowed to have the same hash code. It is even technically allowed that all instances have the same hash code, but if clashes happen too often, it may reduce the efficiency of hash-based data structures like HashSet or HashMap.

If a subclass overrides hashCode, it should override the operator == operator as well to maintain consistency.

Inherited from Object.

Implementation
dart
external int get hashCode;

isCompleted no setter

bool get isCompleted

Whether the future has been completed.

Reflects whether complete or completeError has been called. A true value doesn't necessarily mean that listeners of this future have been invoked yet, either because the completer usually waits until a later microtask to propagate the result, or because complete was called with a future that hasn't completed yet.

When this value is true, complete and completeError must not be called again.

Implementation
dart
bool get isCompleted;

runtimeType no setter inherited

Type get runtimeType

A representation of the runtime type of the object.

Inherited from Object.

Implementation
dart
external Type get runtimeType;

Methods

complete()

void complete([FutureOr<T>? value])

Completes future with the supplied values.

The value must be either a value of type T or a future of type Future<T>. If the value is omitted or null, and T is not nullable, the call to complete throws.

If the value is itself a future, the completer will wait for that future to complete, and complete with the same result, whether it is a success or an error.

Calling complete or completeError must be done at most once.

All listeners on the future are informed about the value.

Implementation
dart
void complete([FutureOr<T>? value]);

completeError()

void completeError(Object error, [StackTrace? stackTrace])

Complete future with an error.

Calling complete or completeError must be done at most once.

Completing a future with an error indicates that an exception was thrown while trying to produce a value.

If error is a Future, the future itself is used as the error value. If you want to complete with the result of the future, you can use:

dart
thisCompleter.complete(theFuture)

or if you only want to handle an error from the future:

dart
theFuture.catchError(thisCompleter.completeError);

The future must have an error handler installed before the call to completeError) or error will be considered an uncaught error.

dart
void doStuff() {
  // Outputs a message like:
  // Uncaught Error: Assertion failed: "future not consumed"
  Completer().completeError(AssertionError('future not consumed'));
}

You can install an error handler through Future.catchError, Future.then or the await operation.

dart
void doStuff() {
  final c = Completer();
  c.future.catchError((e) {
    // Handle the error.
  });
  c.completeError(AssertionError('future not consumed'));
}

See the Zones article for details on uncaught errors.

Implementation
dart
void completeError(Object error, [StackTrace? stackTrace]);

noSuchMethod() inherited

dynamic noSuchMethod(Invocation invocation)

Invoked when a nonexistent method or property is accessed.

A dynamic member invocation can attempt to call a member which doesn't exist on the receiving object. Example:

dart
dynamic object = 1;
object.add(42); // Statically allowed, run-time error

This invalid code will invoke the noSuchMethod method of the integer 1 with an Invocation representing the .add(42) call and arguments (which then throws).

Classes can override noSuchMethod to provide custom behavior for such invalid dynamic invocations.

A class with a non-default noSuchMethod invocation can also omit implementations for members of its interface. Example:

dart
class MockList<T> implements List<T> {
  noSuchMethod(Invocation invocation) {
    log(invocation);
    super.noSuchMethod(invocation); // Will throw.
  }
}
void main() {
  MockList().add(42);
}

This code has no compile-time warnings or errors even though the MockList class has no concrete implementation of any of the List interface methods. Calls to List methods are forwarded to noSuchMethod, so this code will log an invocation similar to Invocation.method(#add, [42]) and then throw.

If a value is returned from noSuchMethod, it becomes the result of the original invocation. If the value is not of a type that can be returned by the original invocation, a type error occurs at the invocation.

The default behavior is to throw a NoSuchMethodError.

Inherited from Object.

Implementation
dart
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
external dynamic noSuchMethod(Invocation invocation);

toString() inherited

String toString()

A string representation of this object.

Some classes have a default textual representation, often paired with a static parse function (like int.parse). These classes will provide the textual representation as their string representation.

Other classes have no meaningful textual representation that a program will care about. Such classes will typically override toString to provide useful information when inspecting the object, mainly for debugging or logging.

Inherited from Object.

Implementation
dart
external String toString();

Operators

operator ==() inherited

bool operator ==(Object other)

The equality operator.

The default behavior for all Objects is to return true if and only if this object and other are the same object.

Override this method to specify a different equality relation on a class. The overriding method must still be an equivalence relation. That is, it must be:

  • Total: It must return a boolean for all arguments. It should never throw.

  • Reflexive: For all objects o, o == o must be true.

  • Symmetric: For all objects o1 and o2, o1 == o2 and o2 == o1 must either both be true, or both be false.

  • Transitive: For all objects o1, o2, and o3, if o1 == o2 and o2 == o3 are true, then o1 == o3 must be true.

The method should also be consistent over time, so whether two objects are equal should only change if at least one of the objects was modified.

If a subclass overrides the equality operator, it should override the hashCode method as well to maintain consistency.

Inherited from Object.

Implementation
dart
external bool operator ==(Object other);