以下の記事の続きです
にある
サンプルコード
import 'dart:convert';
import 'package:http/http.dart' as http;
class Package {
final String name;
final String latestVersion;
final String? description;
Package(this.name, this.latestVersion, {this.description});
@override
String toString() {
return 'Package{name: $name, latestVersion: $latestVersion, description: $description}';
}
}
void main() async {
final httpPackageUrl = Uri.https('dart.dev', '/f/packages/http.json');
final httpPackageResponse = await http.get(httpPackageUrl);
if (httpPackageResponse.statusCode != 200) {
print('Failed to retrieve the http package!');
return;
}
final json = jsonDecode(httpPackageResponse.body);
final package = Package(
json['name'],
json['latestVersion'],
description: json['description'],
);
print(package);
}
の後半 void main() async {~} を理解するのに 「async(エイシンク)」 の意味を芋づる式に掘っていったら長くなってしまったため、この記事に分けました。(冒頭、前回と重複する部分もあり。。(´。`))
〇そもそも async(エイシンク) とは
コールバック関数の形ではなく「awaitを使った見やすい形で非同期処理をその関数内で書いていきますよ」ということを示すキーワード
返り値の型 関数A (引数の型 引数の変数名) async{
↑
処理 👈 非同期処理を書く可能性のある箇所
: 👈 非同期処理を書く可能性のある箇所
: 👈 非同期処理を書く可能性のある箇所
(return 値または変数または式) 👈 非同期処理を書く可能性のある箇所
}
//「引数の型 引数の変数名」「return 値または変数または式」はない場合もある
とあったら、とりあえず関数Aでawaitによる非同期処理が行われることを示す
〇対応するDartの公式docの部分をとりあえず眺めてみる
以下のDartのasyncに関する公式docを読むと次のようにあります
Google・DeepL機械翻訳に少し補足を付け足したもの
非同期プログラミングではコールバック関数(=(書いてある順番に逆らい)ある処理が終わったときに、あとから呼び出してもらう関数 主な参考資料③参照)がよく使用されますが、DartではFutureとStreamというオブジェクトによる代替手段が使われます。
Futureは、処理が開始されてからのある時点で結果が与えられるPromise(主な参考資料②参照)のようなものです。Streamは、イベントなどの値のシーケンスを取得するのに使うオブジェクトです。Future、Stream、その他多くのオブジェクトは、dart:asyncライブラリ(APIリファレンス)]に含まれています。
Future APIやStream APIを必ずしも直接使用する必要はありません。Dart言語は、asyncやawaitなどのキーワードを用いた非同期コーディングをサポートしています。詳細については、非同期プログラミングのチュートリアルをご覧ください。
dart:asyncライブラリはウェブアプリとコマンドラインアプリの両方で動作します。使用するには、dart:asyncを以下のように冒頭に記述してインポートしてください。
import 'dart:async';
async/await だけの使用ならdart:asyncの import は不要
(※原文は「dart:core(Dartでいう標準ライブラリーの1つ、importしなくても使える) がFuture API および Stream APIのクラスをエクスポートするため、 再度 dart:async をインポートする必要はありません。」)
Future
FutureオブジェクトはDartライブラリ全体に登場し、多くの場合、非同期メソッドによって返されるオブジェクトとして使用されます。Futureが完了すると、その値は使用可能になります。
awaitの使用
Future APIを直接使用する前に、代わりにawaitの使用を検討してください。await式を使用するコードは、Future APIを使用するコードよりも理解しやすい場合があります。
次の関数を考えてみましょう。これはFutureのthen()メソッドを使用して、3つの非同期関数を順番に実行します。各関数の完了を待ってから次の関数を実行します。
void runUsingFuture() {
// ...
findEntryPoint()
.then((entryPoint) {
return runExecutable(entryPoint, args);
})
.then(flushThenExit);
}
※上のrunUsingFuture()、findEntryPoint()、runExecutable()、flushThenExitは処理の流れを想像しやすくするための命名であり、例えば、Pythonで言うところの標準モジュールで定義されて説明がされているような関数にはない
それぞれの意味は以下の通り
① findEntryPoint()
・戻り値がFutureの関数
・“エントリーポイント”(アプリの実行開始場所)を探す
② .then((entryPoint) { ... })
・findEntryPoint() が 完了したら 中の処理を実行する
・entryPoint は findEntryPoint() が返した結果
③ runExecutable(entryPoint, args)
・次に実行される処理
・これも戻り値が Future
④ .then(flushThenExit)
・runExecutable() が完了したら flushThenExit を呼ぶ
・flushThenExit は「後片付け+終了処理」
サンプルコード内の流れは以下の通り
findEntryPoint() を実行
▽(終わったら)
runExecutable(entryPoint, args) を実行
▽(終わったら)
flushThenExit() を実行
await 式を使用したこれと同等の以下のコードは、より同期的なコードのように見えます:
Future<void> runUsingAsyncAwait() async {
// ...
var entryPoint = await findEntryPoint();
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
}
非同期関数は、Futureからの例外もキャッチできます。例えば、以下の通りです
var entryPoint = await findEntryPoint();
try {
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
} catch (e) {
// Handle the error...
}
await の使用方法および関連する Dart 言語機能の詳細については、非同期プログラミングチュートリアルを参照してください。
基本的な使用法
then() を使用すると、フューチャーが完了したときに実行されるコードをスケジュールできます。 たとえば、以下の例だと
HTTP リクエストには時間がかかる可能性があるため、Client.read() は Future を返す。then() を使用すると、その Future が完了し、約束された文字列値が利用可能になった時点でコードを実行できる:
String url = "https://example.com";
// △このURLにアクセス、公式Docのここの部分には元々これの記載はないが
// 本来はこういうのがあるはず
httpClient.read(url).then((String result) {
print(result);
});
上のコードの流れは以下の通り
① url へアクセスして レスポンスのbody(文字列)を取得する
httpClient.read(url)
※返り値(=取得したレスポンスのbody(文字列))のデータ型は Future<String>
② 取得結果(body)を result として受け取った時点でそれをprintする
.then((String result) {
print(result);
});
then().catchError() パターンは、非同期版の try-catch 構文と言えます。
重要
then()の結果に対してcatchError()を呼び出すことを必ず確認してください—元のFutureの結果に対してではありません。そうしないと、catchError()は元のFutureの計算からのエラーのみを処理でき、then()で登録されたハンドラーからのエラーは処理できません。
複数の非同期メソッドの連鎖
then()メソッドはFutureを返します。これにより、複数の非同期関数を特定の順序で実行する便利な方法が提供されます。then()に登録されたコールバックがFutureを返す場合、then()はコールバックから返されるFutureと同じ結果で完了するFutureを返します。コールバックが他の型の値を返す場合、then()はその値で完了する新しいFutureを作成します。
Future result = costlyQuery(url);
result
.then((value) => expensiveWork(value))
.then((_) => lengthyComputation())
.then((_) => print('Done!'))
.catchError((exception) {
/* Handle exception... */
});
上記の例では、メソッドは次の順序で実行されます:
① costlyQuery()
② expensiveWork()
③ lengthyComputation()
以下はawaitを使用して記述した同じコードです:
try {
final value = await costlyQuery(url);
await expensiveWork(value);
await lengthyComputation();
print('Done!');
} catch (e) {
/* Handle exception... */
}
複数のFutureを返り値として使われるのを待つ場合
アルゴリズムが多くの非同期関数を呼び出し、それら全てが完了するまで待機する必要がある場合があります。複数のFutureを管理し、完了を待機するには、以下のように Future.wait() スタティックメソッドを使用します:
Future<void> deleteLotsOfFiles() async => ...
Future<void> copyLotsOfFiles() async => ...
Future<void> checksumLotsOfOtherFiles() async => ...
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');
Future.wait() は、渡されたすべてのフューチャーが完了した時点で完了するフューチャーを返します。渡されたフューチャーのいずれかが失敗した場合、その結果と共に完了するか、エラーで完了します。
複数のFutureに対するエラーハンドリング
また、以下の並列操作を待機することもできます:
・Futureを要素とするiterableなオブジェクト
・Futureを位置フィールドとして持つレコード
(≒Futureを要素として持つようなベクトルのようなもの??)
これらの拡張は、提供されたすべてのフューチャーの結果値を含むフューチャーを返します。Future.waitとは異なり、これらはエラー処理も可能にします。
コレクション内のいずれかのFutureがエラーで終了した場合、ParallelWaitErrorの状態で完了を待ちます。これにより、呼び出し元は個々のエラーを処理し、必要に応じて成功した結果を破棄できます。
各Futureの結果値が必要ない場合は、Futureのiterableなオブジェクトに対して wait を使います:
Future<int> delete() async => ...;
Future<String> copy() async => ...;
Future<bool> errorResult() async => ...;
void main() async {
try {
// Wait for each future in a list, returns a list of futures:
var results = await [delete(), copy(), errorResult()].wait;
} on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {
print(e.values[0]); // Prints successful future
print(e.values[1]); // Prints successful future
print(e.values[2]); // Prints null when the result is an error
print(e.errors[0]); // Prints null when the result is successful
print(e.errors[1]); // Prints null when the result is successful
print(e.errors[2]); // Prints error
}
}
各Futureから個別の結果値が必要な場合は、Futureのレコードに対して wait を使用してください。これにより、Futureが異なる型であっても処理できます。
「Stream」というオブジェクトに関する内容はここではほぼ割愛しました。
〇結局何をしているか
上記のドキュメントや主な参考資料を読む限り、
(本来期待した出力になるように)Future(=まだ来てない結果)が解決(完了)するまで await でせき止めている
冒頭のサンプルコードだと
前略
void main() async {
final httpPackageUrl = Uri.https('dart.dev', '/f/packages/http.json');
final httpPackageResponse = await http.get(httpPackageUrl); 👈ここが非同期処理
if (httpPackageResponse.statusCode != 200) {
print('Failed to retrieve the http package!');
return;
}
final json = jsonDecode(httpPackageResponse.body);
後略
※http.get(httpPackageUrl)で前行に書いたURLにアクセスし、そのレスポンス(ここでのFuture)が返ってきて、httpPackageResponseに格納されてから、次のif文に進むようにしている
Dart では http.get() 自体が 非同期 API(処理がすぐには終わらず、結果が “あとで” 手に入る前提で設計されたAPI(関数・メソッド群)の1つ) なので、同期(=非async/await)にはできない。
〇 Futureとは
(HTTP通信のレスポンスの結果など)すぐ返ってこない(必ず遅れても返ってくる)return値のようなものの「総称」 を普通のreturn値と区別するために産まれた概念
※GPT大先生は「型を見れば、どれがFutureかはすぐ分かる」と言っていたが、第三者視点でコードを読む場合、何の処理をしているかを見ないと「Futureが何か」分からない場合がある
.then()などで「返ってきたら(=用意できたら)その後どうするか制御できる」点はreturnにない特徴であることにも注意
学習を進める過程で出てきた用語・再度意味を確認した用語④
①コールバック関数
ある処理が終わったときに後から呼び出してもらう関数
Dartの場合
.then((value) {
// ← これがコールバック関数
})
②非同期処理
結果がすぐに返ってこない処理(ここでは、HTTP通信、ファイル読み書き、DBアクセス、一定時間待つ処理 など)
Future(または Stream)を返す(=扱う)処理 ← 〇(本来の意味)
awaitが「書いてある」処理 ← △(await以外の記法もあり)
主な参考資料
①async / awaitについて、再確認(超初心者向け) ss_shirakiさんZenn
https://zenn.dev/singularity/articles/360d69fde8322d
②非同期処理「Async/Await」の使い方とメリット @nakajima417 さん Qiita
https://qiita.com/nakajima417/items/937509491a7033243e86
③【JavaScript】非同期処理の第一歩:コールバック関数を理解しよう maboさんZenn
https://zenn.dev/mabo23/articles/d6e770b5ad0b7d
④【Flutter】Dartの非同期処理(Future・then) @k-keita(Keita +) さん
https://qiita.com/k-keita/items/d76b232eda68681b2b36
⑤【Flutter】Dartの非同期処理を本気で学ぶ Urasan さんZenn
https://zenn.dev/urasan/articles/f6613470658de1