導入
前回に続いて、roadmap.shのFlutterのAdvanced DartとしてCore Librariesの項目の2番目のasync
についてです。
FlutterはAPIサーバーとのやりとりも多いと考えられるので、通信処理においてasyncを利用することはしばしばあると思われます。
このasyncをマスターすることはモバイルアプリ開発において重要であることは間違いありません。では、内容に入っていきます。
dart:async
非同期プログラミングとDartのFutureおよびStream
Dartでは、非同期プログラミングによく使われるコールバック関数の代わりに、Future
と Stream
オブジェクトを使用します。
Future
は将来のある時点で提供される結果の約束のようなものであり、Stream
はイベントなどの一連の値を取得する方法です。
これらのオブジェクトは dart:async
ライブラリに含まれています。
Dart言語は、async
や await
といったキーワードを使用した非同期コーディングをサポートしています。Future
や Stream
APIを直接使う必要は必ずしもありません。また、dart:core
がこれらのクラスをエクスポートしているため、dart:async
をインポートする必要はありません。
Future
Future
オブジェクトは、非同期メソッドによって返されるオブジェクトとしてDartライブラリに広く存在します。Future
が完了すると、その値が使用可能になります。
await
の使用
Future
API を直接使用する前に、await
を使うことを検討してください。await
式を使用するコードは、Future
API を使うコードよりも理解しやすいことがあります。
Future<void> runUsingAsyncAwait() async {
// ...
var entryPoint = await findEntryPoint();
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
}
async
関数は、Future
からの例外をキャッチすることができます。
var entryPoint = await findEntryPoint();
try {
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
} catch (e) {
// エラー処理...
}
基本的な使用法
then()
メソッドを使用して、Future
が完了したときに実行されるコードをスケジュールできます。
httpClient.read(url).then((String result) {
print(result);
}).catchError((e) {
// エラー処理...
});
複数の非同期メソッドのチェーン
then()
メソッドは Future
を返し、複数の非同期関数を特定の順序で実行するための便利な方法を提供します。
Future result = costlyQuery(url);
result
.then((value) => expensiveWork(value))
.then((_) => lengthyComputation())
.then((_) => print('Done!'))
.catchError((exception) {
/* エラー処理... */
});
上記の例を await
を使って書くと、以下のようになります。
try {
final value = await costlyQuery(url);
await expensiveWork(value);
await lengthyComputation();
print('Done!');
} catch (e) {
/* エラー処理... */
}
複数の Future
の待機
多くの非同期関数を呼び出してそれらがすべて完了するのを待つ必要がある場合は、Future.wait()
メソッドを使用します。
この機能は便利そうです!
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');
複数の Future
のエラーハンドリング
複数の非同期操作を並列に実行し、それぞれの結果を処理する場合には、wait
メソッドを使用してエラーを処理することができます。
try {
var results = await [delete(), copy(), errorResult()].wait;
// 処理...
} on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {
// エラーハンドリング...
}
個別の結果が必要な場合には、レコードを使用して wait
メソッドを呼び出すことができます。
try {
(int, String, bool) result = await (delete(), copy(), errorResult()).wait;
// 処理...
} on ParallelWaitError<(int?, String?, bool?),
(AsyncError?, AsyncError?, AsyncError?)> catch (e) {
// エラーハンドリング...
}
Stream
Stream
オブジェクトは、DartのAPI全体にわたって使用されており、データのシーケンスを表します。たとえば、ボタンのクリックなどのHTMLイベントはストリームを使用して配信されます。また、ファイルをストリームとして読み取ることも可能です。
非同期forループの使用
非同期forループ (await for
) は、Stream
API の代わりに使用できる場合があります。
void main(List<String> arguments) async {
if (await FileSystemEntity.isDirectory(searchPath)) {
final startingDir = Directory(searchPath);
await for (final entity in startingDir.list()) {
if (entity is File) {
searchFile(entity, searchTerms);
}
}
} else {
searchFile(File(searchPath), searchTerms);
}
}
ストリームデータのリスニング
await for
を使用するか、listen()
メソッドを使用してストリームにサブスクライブします。
submitButton.onClick.listen((e) {
submitData();
});
ストリームデータの変換
transform()
メソッドを使用して、ストリームのデータ形式を変換できます。
var lines = inputStream
.transform(utf8.decoder)
.transform(const LineSplitter());
エラーと完了の処理
await for
を使用する場合、try-catch
を使用してエラーを処理し、ストリームが閉じられた後に実行されるコードを記述します。
Future<void> readFileAwaitFor() async {
var config = File('config.txt');
Stream<List<int>> inputStream = config.openRead();
var lines = inputStream
.transform(utf8.decoder)
.transform(const LineSplitter());
try {
await for (final line in lines) {
print('Got ${line.length} characters from stream');
}
print('file is now closed');
} catch (e) {
print(e);
}
}
Stream
API を使用する場合、onError
リスナーを登録してエラーを処理し、ストリームが閉じられた後のコードを onDone
リスナーに登録します。
inputStream.transform(utf8.decoder).transform(const LineSplitter()).listen(
(String line) {
print('Got ${line.length} characters from stream');
}, onDone: () {
print('file is now closed');
}, onError: (e) {
print(e);
});
終わりに
今回は、Dartのasync
に関する基本的な概念と使い方の紹介しました。非同期プログラミングは、モバイルアプリ開発において効率的かつスムーズなユーザーエクスペリエンスを提供するために重要な要素です。Future
やStream
を活用して、安定したアプリケーションを構築していきたいです。