UIをブロックしたり、うっかりリクエストを再発火させたりすることなく、Future、async/await、FutureBuilderを使いこなしましょう。
このレッスンを Kodokon で開くDartはあなたのアプリケーションを、イベントループに駆動される単一のスレッドの上で実行します。Futureはスレッドではありません。これからやってくる値の約束です。awaitはそのスレッドをブロックすることは決してありません。現在の関数を中断し、制御をループへ返し(UIは動き続けます)、結果が届いたときに再開します。当然の帰結として、async/awaitは純粋な計算については何も並列化しません。CPUを集中的に使う作業には、アイソレート(isolate)が必要です。
Future<double> fetchRate() async {
await Future<void>.delayed(
const Duration(milliseconds: 300),
);
return 1.08;
}
Future<void> main() async {
try {
final rate = await fetchRate();
print('EUR/USD rate: $rate');
} on Exception catch (error) {
print('Rate loading failed: $error');
}
}連続する二つのawaitは直列に実行されます。二つ目は一つ目のあとにしか始まりません。処理が互いに独立しているときは、Future.waitでまとめて起動しましょう。知っておくべきトレードオフは、Future.waitがフェイルファストであることです。最初のエラーが全体を拒否し、ほかのfutureの結果は、たとえ成功していても失われます。部分的な結果が必要なら、それぞれのfutureのなかでエラーを処理しましょう。
Future<String> fetchProfile() async => 'profile';
Future<String> fetchOrders() async => 'orders';
Future<void> main() async {
final [profile, orders] = await Future.wait(
[fetchProfile(), fetchOrders()],
);
print('$profile / $orders');
}UIの側では、FutureBuilderがsnapshotを介してFutureをbuildにつなぎます。現場での一番の落とし穴は、futureをbuildのなかで直接作ってしまうことです。リビルドのたびに、親からの単なるsetState一つで十分ですが、リクエストが再発火してしまいます。futureはinitStateに保存し、常にデータより先にエラーをテストしましょう。
import 'package:flutter/material.dart';
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
@override
State<ProfileScreen> createState() =>
_ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
late final Future<String> _nameFuture;
@override
void initState() {
super.initState();
_nameFuture = _loadName();
}
Future<String> _loadName() async {
await Future<void>.delayed(
const Duration(seconds: 1),
);
return 'Ada Lovelace';
}
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _nameFuture,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Text(snapshot.data!);
},
);
}
}awaitはメインスレッド上で実際には何をしますか?Futureが解決するまでスレッドをブロックする。Future.wait([a(), b()])で、a()が失敗するとどうなりますか?Future.waitはa()のエラーで拒否され、b()の結果は失われる。Future.waitはb()が終わるのを待ち、その結果だけを返す。b()が成功する限り、エラーは無視される。FutureBuilderでfuture: fetchUser()をbuildのなかに直接書くのを避けるのはなぜですか?buildは非同期呼び出しを含められず、コードがコンパイルされないから。Futureが作られ、そのためネットワークリクエストが再発火するから。snapshotが待機状態に固まったままになるから。