Closed19

DartのFutureについて調べる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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>'
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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

上記は実行結果、予想通り

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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として表示されていることがわかる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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

意図したように動作していることがわかる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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が同期的に呼び出されていることがわかる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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

実行してみたところ想像通りだった

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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のテスト機能がすごい

https://github.com/dart-lang/dart-pad/wiki/Embedding-Guide#testing-the-users-code

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Handling errors

エラー処理

同期コードと同様にtry {} catch (err) {}でくくる

そういえばerrの型を指定するにはどうすれば良いのか

https://dart.dev/guides/language/language-tour#catch

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キーワードを使えばいいんですね、勉強になりました

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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

予想通りだった

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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();
  }
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

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().

解答

// 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();
  }
}
このスクラップは2023/01/10にクローズされました