dart-async-programming について
Future と Stream を使った Dart の非同期コード作成を支援するスキルです。読みやすい制御フロー、エラー処理、並行実行、Stream のライフサイクル管理を扱います。
主な機能
- 01async と await の優先
- 02try-catch によるエラー処理
- 03独立した Future の並行実行
- 04await for による Stream の処理
- 05StreamController インスタンスのクローズ
活用シーン
- 01非同期データ取得の実装
- 02独立したリクエストの並行実行
- 03Stream イベントの逐次処理
- 04StreamController のリソースリーク防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dart-lang/ skills --skill dart-async-programmingインストール後、Claude Code で/dart-async-programmingを入力して読み込まれたか確認します。
詳細
非同期 Dart コードの記述
目次
基本ガイドライン
最新の宣言的なパターンを使用して、非同期 Dart コードを記述してください。従来のコールバックベースの手法は避けてください。すべてのネットワーク、ファイル I/O、データベース操作は非同期であると想定してください。
async/awaitを使用する:生の.then()、.catchError()、.whenComplete()のチェーンより、常にasyncとawaitを優先してください。これにより実行フローがシンプルになり、可読性が向上します。- エラーを適切に処理する:例外を処理するため、すべての
await呼び出しをtry-catchブロックで囲んでください。処理されない Future の拒否に頼らないでください。 - 並行して実行する:独立した複数の Future を順番に待機するのではなく、
Future.waitを使用して同時に開始し、待機してください。 - Stream を順次処理する:一時停止や再開など、低レベルのサブスクリプション管理が必要な場合を除き、Stream の処理には
.forEach()や.listen()よりawait forを優先してください。 - メモリーリークを防ぐ:
StreamControllerが不要になったとき、または所有するクラスが破棄されるときは、必ず.close()を呼び出してください。
Future の処理
順次実行と並行実行
操作が互いに依存する場合は順番に待機してください。操作が独立している場合は、実行時間を最適化するために同時に開始してください。
順次実行(依存関係あり):
final user = await fetchUser(id);
final profile = await fetchProfile(user.profileId);
並行実行(独立):
final results = await Future.wait([
fetchUserData(id),
fetchUserPreferences(id),
fetchUserPermissions(id),
]);
エラー処理
async 関数内では、必ず標準的な try-catch-finally ブロックを使用してください。
try {
final data = await fetchData();
process(data);
} on NetworkException catch (e) {
handleNetworkError(e);
} catch (e) {
handleGenericError(e);
} finally {
cleanupResources();
}
Stream の管理
Stream の消費
非同期 for ループ(await for)を使用して、Stream イベントを順次処理してください。これにより、Stream の完了が自動的に処理され、エラー処理のための try-catch とも自然に連携できます。
try {
await for (final chunk in fileStream) {
processChunk(chunk);
}
} catch (e) {
logError('Stream processing failed: $e');
}
Stream の作成と管理
StreamController を使用してカスタム Stream を作成するときは、メモリーリークを防ぐため、ライフサイクルを適切に管理してください。
class DataProvider {
final _controller = StreamController<Data>.broadcast();
Stream<Data> get dataStream => _controller.stream;
void updateData(Data newData) {
if (!_controller.isClosed) {
_controller.add(newData);
}
}
void dispose() {
_controller.close(); // Mandatory cleanup
}
}
ワークフロー
タスクの進捗:非同期データフェッチャーの実装
非同期データ取得ロジックを実装する際に、進捗を記録するため、このチェックリストをコピーしてください。
- 必要な操作が順次実行か独立実行かを確認する。
- 関数に
asyncキーワードを付け、Future<T>を返す。 - 非同期操作を
try-catchブロックで囲む。 - 独立している場合は、
Future.waitを使って Future をまとめる。 - 順次実行の場合は、各ステップで
awaitを使用する。 -
on ExceptionType catch (e)を使って特定の例外を処理する。 - バリデーターを実行する → エラーを確認する → 処理されていない Promise の拒否を修正する。
タスクの進捗:Stream コンシューマーの実装
Stream を消費する際に、進捗を記録するため、このチェックリストをコピーしてください。
- Stream のソース(単一サブスクリプションかブロードキャストか)を確認する。
- 消費関数に
asyncを付ける。 - 消費ロジックを
try-catchブロックで囲む。 - Stream を反復処理する
await forループを実装する。 - 特定の終了条件に達した場合は、ループを中断するか、戻るようにする。
-
StreamControllerを管理する場合は、dispose()またはclose()メソッドを実装する。
例
アンチパターンとベストプラクティス:Future
アンチパターン(使用しないでください):
Future<void> loadUserData() {
return fetchUser()
.then((user) {
return fetchProfile(user.id)
.then((profile) {
print('User: ${user.name}, Profile: ${profile.status}');
});
})
.catchError((error) {
print('Error: $error');
});
}
ベストプラクティス:
Future<void> loadUserData() async {
try {
final user = await fetchUser();
final profile = await fetchProfile(user.id);
print('User: ${user.name}, Profile: ${profile.status}');
} catch (error) {
print('Error: $error');
}
}
アンチパターンとベストプラクティス:Stream
アンチパターン(使用しないでください):
void processEvents(Stream<Event> eventStream) {
eventStream.listen(
(event) => handleEvent(event),
onError: (error) => print('Error: $error'),
);
}
ベストプラクティス:
Future<void> processEvents(Stream<Event> eventStream) async {
try {
await for (final event in eventStream) {
handleEvent(event);
}
} catch (error) {
print('Error: $error');
}
}
関連スキル
dart-idiomatic-usagedart-concurrency-isolates