DartのFutureについて調べる

Dart公式ドキュメントの下記のページを読んでいく
必要に応じてAPIリファレンスを参照する
動作確認にはDartPadを使用する

JavaScriptのPromiseみたいなものだと考えているが果たしてどうやら

おおむねその通りだった

Why asynchronous code matters
なぜ非同期コードが重要なのか
非同期の結果が単一ならFuture、複数ならStreamを使用する、ここは想定の通り

Example: Incorrectly using an asynchronous function
非同期関数の誤った使い方
下記の例で間違った部分を探すように言われている
String createOrderMessage() {
var order = fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() =>
// Imagine that this function is more complex and slow.
Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
void main() {
print(createOrderMessage());
}
おそらくcreateOrderMessage関数とmain関数に
- 戻り値がFutureではないこと
- 呼び出しにawaitを使っていないこと
- asyncがないこと
上記のコードを実行すると下記の結果が表示される
Your order is: Instance of '_Future<String>'

What is a future?
futureとは何か
futureはFutureクラスのインスタンス、非同期操作の結果を表現する

Uncompleted
未完了状態
futureには未完了uncompletedと完了completedの2つの状態がある、初期状態は未完了

Completed
完了状態
futureが完了すると値またはエラーのいずれかが生成される

Example: Introducing futures
futureの導入
下記のコード例が示されている
Future<void> fetchUserOrder() {
// Imagine that this function is fetching user info from another service or database.
return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
awaitしていないのでFetching user order... → Large Latteの順序で表示されそう
Fetching user order...
(2秒待機)
Large Latte
上記は実行結果、予想通り

Example: Completing with an error
エラーで完了する
下記のコード例では例外は捕捉されるのだろうか
Future<void> fetchUserOrder() {
// Imagine that this function is fetching user info but encounters a bug
return Future.delayed(const Duration(seconds: 2),
() => throw Exception('Logout failed: user ID is invalid'));
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
実行結果は下記の通り
Fetching user order...
Uncaught Error: Exception: Logout failed: user ID is invalid
Uncaught Errorとして表示されていることがわかる

Working with futures: async and await
futureを操作する:asyncとawait
await
を使うと結果またはエラーを待機できる
await
を使うには下記のように準備する
- 戻り値をFutureにする
- ボディの直前に
async
キーワードを入れる
Future<void> main() async { ··· }
せっかくなので練習で最初の例を書き換えてみる
Future<String> createOrderMessage() async {
var order = await fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() =>
// Imagine that this function is more complex and slow.
Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
Future<void> main() async {
print(await createOrderMessage());
}
実行結果
Your order is: Large Latte
意図したように動作していることがわかる

Execution flow with async and await
asyncとawaitによる実行フロー
async関数は最初のawaitまで同期的に実行される
Future<void> func() async {
print("1");
await Future.delayed(Duration(seconds: 1));
print("2");
}
void main() {
print("3");
func();
print("4");
}
実行結果
3
1
4
(1秒待機)
2
4の前に1が表示されていることからfuncが同期的に呼び出されていることがわかる

Example: Execution within async functions
async関数内の実行
下記のコードの実行結果を想像してみる
Future<void> printOrderMessage() async {
print('Awaiting user order...');
var order = await fetchUserOrder();
print('Your order is: $order');
}
Future<String> fetchUserOrder() {
// Imagine that this function is more complex and slow.
return Future.delayed(const Duration(seconds: 4), () => 'Large Latte');
}
void main() async {
countSeconds(4);
await printOrderMessage();
}
// You can ignore this function - it's here to visualize delay time in this example.
void countSeconds(int s) {
for (var i = 1; i <= s; i++) {
Future.delayed(Duration(seconds: i), () => print(i));
}
}
想像するに下記だろうか
Awaiting user order...
(1秒待機)
1
(1秒待機)
2
(1秒待機)
3
(1秒待機)
4
Your order is Large Latte
実行してみたところ想像通りだった

Exercise: Practice using async and await
asyncとawaitを使う練習
下記の2つの関数を使ってreportUserRole関数とreportLogins関数を完成させる
- Future<String> fetchRole
- Future<int> fetchLoginAmount
// Part 1
// You can call the provided async function fetchRole()
// to return the user role.
Future<String> reportUserRole() async {
TODO('Your implementation goes here.');
}
// Part 2
// Implement reportLogins here
// You can call the provided async function fetchLoginAmount()
// to return the number of times that the user has logged in.
reportLogins() {}
答えは下記の通り
// Part 1
// You can call the provided async function fetchRole()
// to return the user role.
Future<String> reportUserRole() async {
final role = await fetchRole();
return "User role: $role";
}
// Part 2
// Implement reportLogins here
// You can call the provided async function fetchLoginAmount()
// to return the number of times that the user has logged in.
Future<String> reportLogins() async {
final loginAmount = await fetchLoginAmount();
return "Total number of logins: $loginAmount";
}
DartPadのテスト機能がすごい

Handling errors
エラー処理
同期コードと同様にtry {} catch (err) {}
でくくる
そういえばerr
の型を指定するにはどうすれば良いのか
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
}
on
キーワードを使えばいいんですね、勉強になりました

Example: async and await with try-catch
try-catchを使用したasync/await
コード
Future<void> printOrderMessage() async {
try {
print('Awaiting user order...');
var order = await fetchUserOrder();
print(order);
} catch (err) {
print('Caught error: $err');
}
}
Future<String> fetchUserOrder() {
// Imagine that this function is more complex.
var str = Future.delayed(
const Duration(seconds: 4),
() => throw 'Cannot locate user order');
return str;
}
void main() async {
await printOrderMessage();
}
実行結果の予想
Awaiting user order...
(4秒待機)
Caught error: Cannot locate user order
予想通りだった

Exercise: Practice handling errors
エラー処理の練習
コード
// Implement changeUsername here
changeUsername() {}
問題
- Future<String> fetchNewUsername() を呼び出して結果を返す
- エラーが発生したら文字列に変換して返す
解答
// Implement changeUsername here
Future<String> changeUsername() async {
try {
return await fetchNewUsername();
} catch (err) {
return err.toString();
}
}

Exercise: Putting it all together
すべてをまとめる
コード
// Part 1
addHello(String user) {}
// Part 2
// You can call the provided async function fetchUsername()
// to return the username.
greetUser() {}
// Part 3
// You can call the provided async function logoutUser()
// to log out the user.
sayGoodbye() {}
問題
- Part 1: addHello()
- Write a function addHello() that takes a single String argument.
- addHello() returns its String argument preceded by 'Hello '.
- Example: addHello('Jon') returns 'Hello Jon'.
- Part 2: greetUser()
- Write a function greetUser() that takes no arguments.
- To get the username, greetUser() calls the provided asynchronous function fetchUsername().
- greetUser() creates a greeting for the user by calling addHello(), passing it the username, and returning the result.
- Example: If fetchUsername() returns 'Jenny', then greetUser() returns 'Hello Jenny'.
- Part 3: sayGoodbye()
- Write a function sayGoodbye() that does the following:
- Takes no arguments.
- Catches any errors.
- Calls the provided asynchronous function logoutUser().
- If logoutUser() fails, sayGoodbye() returns any string you like.
- If logoutUser() succeeds, sayGoodbye() returns the string '<result> Thanks, see you next time', where <result> is the string value returned by calling logoutUser().
- Write a function sayGoodbye() that does the following:
解答
// Part 1
String addHello(String user) {
return "Hello $user";
}
// Part 2
// You can call the provided async function fetchUsername()
// to return the username.
Future<String> greetUser() async {
final username = await fetchUsername();
return addHello(username);
}
// Part 3
// You can call the provided async function logoutUser()
// to log out the user.
Future<String> sayGoodbye() async {
try {
final result = await logoutUser();
return "$result Thanks, see you next time";
} catch (err) {
return err.toString();
}
}

おわりに
以上をもって一旦クローズ、次はStreamについて調べる