在 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
。
我有一个 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
。