Skip to main content

Unboxing Packages: async Part 1

Writing asynchronous code is hard. Even with Dart’s lovely async/await syntax, asynchrony inherently involves code running in a nondeterministic order, intermingled in a way that’s often difficult to understand and debug. Because there are so many possible ways to execute a program, and which one happens is so contingent on little details of timing, it’s not even possible to test asynchronous code in the same exhaustive way you might test something synchronous.

This is why it’s so important to have abstractions and utilities that are simple and robust and can be used as building blocks for more complex programs. The core libraries provide the most fundamental abstractions: Future for an asynchronous value, and Stream for an asynchronous collection or signal. But it’s up to packages to build the next layer on top of those fundamental primitives.

The async package is where the next-most-basic abstractions live. It’s a core library expansion pack that contains APIs that are just a little more advanced than would fit in dart:async itself. The classes it exposes are well-tested and as straightforward as you can hope from asynchrony, and using them properly can make your code vastly clearer and more reliable.

The async package is just chock-full of cool stuff. So full, in fact, that it won’t all fit in a single blog post—I have to split it up. Today I’ll mostly talk about APIs that deal with individual values. I’ll save Streams for the next post, and I may even need a third to cover the whole package.


If you’re writing synchronous code and you want to provide access to a value in a class, you just define it as a field. If that value should only be computed when it’s accessed, you can memoize it by writing a getter that sets a field the first time it’s accessed.

String get contents {
  if (_contents == null) _contents = new File(this.path).readAsStringSync();
  return _contents;
String _contents;

But what if the value can only be computed asynchronously? You still want to compute it lazily, but you don’t want to have every access recompute it from scratch. Once the computation has started, future accesses should return the same future. You can implement this manually, but it’s a pain; AsyncMemoizer makes it easy:

Future<String> get contents => _contentsMemo.runOnce(() {
  return new File(this.path).readAsString();
final _contentsMemo = new AsyncMemoizer<String>();

The first time you call runOnce() on a given memoizer, it invokes the callback and returns its return value as a Future. After that, all calls to runOnce() don’t use the callback at all; they just return the same value. Make sure you always pass the same callback—otherwise you may not be able to tell which one will run!

Some readers may be wondering why the callback isn’t passed to the constructor. After all, it’s only invoked once—future calls to runOnce() just throw it away! The answer is that we want code using AsyncMemoizer to be able to look like the snippet above, with the code the getter executes right there in the body of the getter. We also want the memoizer itself to be usable as a final variable, which means that its constructor couldn’t refer to other fields in the class (like this.path).

Another common use of AsyncMemoizer doesn’t even use the return value of the callback. It just uses the memoizer to ensure that a method’s body is only executed once, and that it always returns the same future. Which, it turns out, is exactly how close() methods work for a lot of classes.

bool get isClosed => _closeMemo.hasRun;

Future close() => _closeMemo.runOnce(() async {
  await _engine.close();
  await _loader.close();
final _closeMemo = new AsyncMemoizer();

Notice the isClosed getter in that example. AsyncMemoizer exposes a hasRun property specifically to make that sort of getter possible: hasRun returns true if runOnce() has been called, regardless of whether the callback has completed. It also has a future property, which returns the same future as runOnce() without actually running the memoizer.


When working asynchronously, values and errors are often treated as two sides of the same coin. A future completes with either a value or an error, and a stream emits value events and error events. But in the synchronous world, errors are completely different than values, and that can cause friction when moving between synchronous and asynchronous code.

That’s what the Result class is for. Each Result is either a value or an error, and whichever it is is accessible synchronously. It has two subclasses, one for each state: ValueResult has a value getter, and ErrorResult has a error and stackTrace getters. If you have a Result, you can use isValue and isError to easily check its type, followed by asValue and asError to easily cast it to the proper type.

You can create Results manually using new Result.value() or new Result.error(), but there are utility functions to convert from asynchronous objects: Result.capture() turns a Future into a Future<Result>, and Result.captureStream() turns a Stream into a Stream<Result>. Errors that would have been emitted using the normal future or stream error channels are turned into ErrorResults instead.

You can also reverse this process using Result.release() and Result.releaseStream(). These take a Future<Result> and a Stream<Result>, respectively, and convert ErrorResults to normal error events.

Result has some instance methods for moving back to the async world too. The asFuture getter returns a future that completes to the Result’s value or error, and complete() completes a Completer so that its future does the same. For streams, you can use addTo() to add the value or the error to an EventSink.


Sometimes you want limited synchronous access to a future. Maybe you want to do something with its value if it exists, but not wait for it if it doesn’t. The ResultFuture class makes this possible by exposing a result getter. Before the future has completed, this is just null; afterwards, it’s a Result. Otherwise, the ResultFuture is just a normal future that works like futures work.

// A Shelf handler that forwards requests to a `Future<Handler>`.
class AsyncHandler {
  final ResultFuture<shelf.Handler> _future;

  AsyncHandler(Future<shelf.Handler> future) : _future = new ResultFuture(future);

  call(shelf.Request request) {
    if (_future.result == null) {
      return _future.then((handler) => handler(request));

    // Because [_future]'s a [Future], we can return it to throw error.
    if (_future.result.isError) return _future;

    return _future.result.asValue.value(request);


One cool feature of streams in Dart is that their subscriptions can be cancelled. In addition to stopping any more events callbacks from firing, this indicates to the stream producer that it can stop generating events at all. Unfortunately, there’s no similar facility for futures, which is where CancelableOperation comes in.

A CancelableOperation represents an asynchronous operation that will ultimately produce a single value which is exposed as a future (and which may complete to null). It can also be canceled, which causes the value future never to complete and lets the code that created it know to stop work on the operation.

Normally when a CancelableOperation is canceled, it just doesn’t complete—which is analogous to a stream subscription not emitting any events once it’s canceled. But sometimes, especially when using async/await, this isn’t what you want. In that case, you can call valueOrCancellation(), which returns a future that completes even if the operation was canceled. By default it completes to null, but you can pass in a custom value if you want.

There are two ways to create a CancelableOperation. If you already have a value future and you just want to wrap it, you can call new CancelableOperation.fromFuture(). This also takes an onCancel callback that is called if the operation is canceled.

CancelableOperation runSuite(String path) {
  var suite;
  var canceled = false;
  return new CancelableOperation(() async {
    suite = await loadSuite(path);
    if (canceled) return null;

  }(), onCancel: () {
    canceled = true;
    return suite?.close();

If the onCancel callback returns a Future, it will be forwarded to the return value for the call to cancel(). This is just like how cancelling a StreamSubscription works, except that for consistency CancelableOperation.cancel() never returns null.

You can also create a CancelableOperation using a CancelableCompleter. This works a lot like a Completer for a future: it has complete() and completeError() methods, and it exposes the operation it controls through the operation getter. But its constructor takes an onCancel callback that’s called if the operation is canceled, and it has an isCanceled getter.

/// Like [Stream.first], but cancelable.
CancelableOperation cancelableFirst(Stream stream) {
  var subscription;
  var completer = new CancelableCompleter(
    onCancel: () => subscription.cancel());

  subscription = stream.listen((value) {
  }, onError: (error, stackTrace) {
    completer.completeError(error, stackTrace);

  return completer.operation;


Fun fact: at least three different versions of the FutureGroup class existed across the Dart world before a canonical implementation finally ended up in the async package. That’s a pretty good indication that it’s a broadly-applicable abstraction!

A FutureGroup collects a bunch of input futures and exposes a single output future that completes when all the inputs have completed. Inputs are added using add(), and the output is called future.

Once you’ve added all the futures you want to the group, call close() to tell it that no more are coming. Some astute students of the core libraries will recognize the pattern of add() followed by close() as the hallmark of a sink—and indeed, FutureGroup implements Sink.

// An engine for running tests.
class Engine {
  final _group = new FutureGroup();

  // Completes when all tests are done.
  Future get onDone => _group.future;

  void addTest(Test test) {

  void noMoreTests() {

If all the futures that have been added to a FutureGroup have completed but it hasn’t been closed, we say that the group is idle. You can tell whether a group is idle using the isIdle getter. You can also use the onIdle stream to get an event whenever the group becomes idle—that is, whenever the last running future completes.


I hope I’ve whet your appetite for asynchrony, because there’s plenty more to come. Working with individual values is useful, but the bulk of the package—and, in my opinion, some of the coolest stuff it contains—has to do with streams. Check back in two weeks, when I tell you all about it!

Popular posts from this blog

A stronger Dart for everyone

We are constantly asking ourselves:
How do we make developers even more productive when writing Dart apps? We believe that a critical part of the answer to this question is to make strongmode – a sound static type system for Dart – the standard for all Dart developers.

Teams that use Dart to build apps like Soundtrap, AdWords, AdSense, and Greentea say they really enjoy using strong mode features, such as early error detection. In fact, teams that have switched completely to strong mode cite not only early error detection but also better code readability and maintainability as major benefits. We hear this both from small teams and – even more so – from large teams with hundreds of developers writing and maintaining millions of lines of Dart code. As Björn Sperber from Soundtrap says,
Strong mode and the smooth integration with IntelliJ is a joy to use and a huge improvement. If you’ve tried out Flutter, you’ve already used strong mode checks from the Dart analyzer.

Given the benefits …

AngularDart 3.0: Easy upgrade, better performance

AngularDart 3.0 is now available. It brings better performance and smaller generated code, while also making you more productive.

Version 3.0 is an evolution: although it has some breaking changes (detailed below), it is a smooth upgrade due to minimal public API adjustments. Most of the progress is under the hood—in code quality, stability, generated code size, performance, and developer experience.

Code quality:
2731 instances of making the framework code more type safe (using sound Dart).The AngularDart framework code size is down by 12%.Many additional style updates to the codebase:Changed to use idiomatic <T> for generic methods.Removed NgZoneImpl, all the code exists in NgZone now.Stability:
Many CSS encapsulation fixes due to update with csslib package.Fixed bugs with IE11.

For the Mail sample app, we see 30% faster time-to-interactive (currently 3812 ms on a simulated 3G connection, measured via Lighthouse).Our large app benchmark shows 2x faster render times of…

Dart 1.24: Faster edit-refresh cycle on the web & new function type syntax

Dart 1.24 is now available. It includes the Dart Development Compiler and supports a new generic function type syntax. Get it now!

Figure 1: DDC debugging in Chrome.

Some notable changes in this release:
pub serve now has support for the Dart Development Compiler. Unlike dart2js, this new compiler is modular, which allows pub to do incremental re-builds for pub serve.In practice what that means is you can edit your Dart files, refresh in Chrome (or other supported browsers), and see your edits almost immediately. This is because pub is only recompiling your package, not all packages that you depend on.There is one caveat with the new compiler, which is that your package and your dependencies must all be strong mode clean.You can also use the new compiler to run your tests in Chrome much more quickly than you can with dart2js.Read more in the changelog.You can now publish packages that depend on the Flutter SDK to pub. Moreover, has started tagging Flutter plugins with …