在 Dart 中等待异步初始化的最优雅方式

Most elegant way to wait for async initialization in Dart

我有一个 class 负责我所有的 API/Database 查询。 class 的所有调用和初始化都是异步方法。

我想提供的合同是调用者必须尽早调用 [initialize],但他们不必等待它,然后他们可以调用任何 API 以后需要时使用方法。

我的大概是这样的:

class MyApi {

  late final ApiConnection _connection;
  late final Future<void> _initialized;

  void initialize(...) async {
    _initialized = Future<void>(() async {
      // expensive initialization that sets _connection
    });
    await _initialized;
  }

  Future<bool> someQuery(...) async {
    await _initialized;
    // expensive async query that uses _connection
  }

  Future<int> someOtherQuery(...) async {
    await _initialized;
    // expensive async query that uses _connection
  }

}

这满足了我想要的调用者的良好契约,但在每个方法开始处重复 await _initialized; 行的实现中感觉非常样板化。有没有更优雅的方式达到同样的效果?

除了使用 code-generation,我认为没有什么好方法可以自动将样板文件添加到您的所有方法中。

但是,根据 _connection 的初始化方式,您或许可以更改:

  late final ApiConnection _connection;
  late final Future<void> _initialized;

类似于:

  late final Future<ApiConnection> _connection = _initializeConnection(...);

并去掉 _initialized 标志。这样,您的样板将从:

  Future<bool> someQuery(...) async {
    await _initialized;
    // expensive async query that uses `_connection`

至:

  Future<bool> someQuery(...) async {
    var connection = await _connection;
    // expensive async query that uses `connection`

这可能看起来不是很大的改进,但实际上要小得多 error-prone。使用您当前使用 await _initialized; 的方法,任何意外遗漏的方法都可能在运行时因 LateInitializationError 过早访问 _connection 而失败。这种失败也很容易被忽视,因为失败取决于调用方法的顺序。例如,如果您有:

Future<bool> goodQuery() async {
  await _initialized;
  return _connection.doSomething();
}

Future<bool> badQuery() async {
  // Oops, forgot `await _initialized;`.
  return _connection.doSomething();
}

然后调用

var result1 = await goodQuery();
var result2 = await badQuery();

会成功,但是

var result2 = await badQuery();
var result1 = await goodQuery();

会失败。

相比之下,如果您可以改用 var connection = await _connection;,那么调用者自然会被迫包含该样板文件。任何意外省略样板并尝试直接使用 _connection 的调用者都会在 编译 时失败,因为尝试将 Future<ApiConnection> 用作 ApiConnection