メインコンテンツにスキップ

非同期プログラミング: Future、async、await

このチュートリアルでは、Futureおよびasyncawaitキーワードを使用して非同期コードを記述する方法を学びます。埋め込みDartPadエディターを使用して、サンプルコードを実行し、演習を完了することで、知識をテストできます。

このチュートリアルを最大限に活用するために、以下の知識があると役立ちます。

このチュートリアルで扱う内容

  • asyncおよびawaitキーワードの使い方と使用時期。
  • asyncawaitの使用が実行順序にどのように影響するか。
  • async関数でtry-catch式を使用して非同期呼び出しからのエラーを処理する方法。

このチュートリアルの完了にかかる推定時間: 40〜60分。

このチュートリアルの演習には、部分的に完了したコードスニペットが含まれています。DartPadを使用してコードを完成させ、「実行」ボタンをクリックすることで、知識をテストできます。main関数またはそれ以降のテストコードは編集しないでください

ヘルプが必要な場合は、各演習の後の「ヒント」または「解答」ドロップダウンを展開してください。

非同期コードが重要な理由

#

非同期操作により、プログラムは別の操作が完了するのを待っている間に作業を完了できます。一般的な非同期操作を次に示します。

  • ネットワーク経由でのデータ取得。
  • データベースへの書き込み。
  • ファイルからのデータ読み込み。

このような非同期計算は、通常、結果をFutureとして、または結果が複数部分にわたる場合はStreamとして提供します。これらの計算はプログラムに非同期性をもたらします。この初期の非同期性に対応するために、他の通常のDart関数も非同期にする必要があります。

これらの非同期結果と対話するために、asyncおよびawaitキーワードを使用できます。ほとんどの非同期関数は、純粋な非同期計算に(おそらく深く)依存するasync Dart関数にすぎません。

例: 非同期関数の誤った使用方法

#

次の例は、非同期関数(fetchUserOrder())を誤って使用する方法を示しています。後でasyncawaitを使用して例を修正します。この例を実行する前に、問題点を見つけようとしてみてください。出力は何になると思いますか?

// This example shows how *not* to write asynchronous Dart code.

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());
}

この例がfetchUserOrder()が最終的に生成する値を印刷できないのは、次の理由によります。

  • fetchUserOrder()は、遅延後にユーザーの注文の説明である「Large Latte」という文字列を提供する非同期関数です。
  • ユーザーの注文を取得するために、createOrderMessage()fetchUserOrder()を呼び出し、それが完了するのを待つ必要があります。createOrderMessage()fetchUserOrder()の完了を待たないため、createOrderMessage()fetchUserOrder()が最終的に提供する文字列値を取得できません。
  • 代わりに、createOrderMessage()は、実行される保留中の作業の表現である未完了のFutureを取得します。Futureについては次のセクションで詳しく説明します。
  • createOrderMessage()がユーザーの注文を示す値を取得できないため、例はコンソールに "Large Latte" を印刷できず、代わりに "Your order is: Instance of '_Future<String>'" と印刷されます。

次のセクションでは、FutureとFutureの操作(asyncawaitの使用)について学び、fetchUserOrder()が希望の値("Large Latte")をコンソールに印刷するために必要なコードを記述できるようになります。

Futureとは?

#

Future(小文字の"f")は、Future(大文字の"F")クラスのインスタンスです。Futureは非同期操作の結果を表し、未完了または完了の2つの状態を持つことができます。

未完了

#

非同期関数を呼び出すと、未完了のFutureが返されます。そのFutureは、関数の非同期操作が完了するか、エラーをスローするのを待っています。

完了

#

非同期操作が成功すると、Futureは値で完了します。それ以外の場合は、エラーで完了します。

値による完了

#

Future<T>型のFutureは、T型の値で完了します。たとえば、Future<String>型のFutureは文字列値を生成します。Futureが使用可能な値を生成しない場合、Futureの型はFuture<void>になります。

エラーによる完了

#

関数によって実行された非同期操作が何らかの理由で失敗した場合、Futureはエラーで完了します。

例: Futureの導入

#

次の例では、fetchUserOrder()はコンソールに印刷した後に完了するFutureを返します。使用可能な値を返さないため、fetchUserOrder()の型はFuture<void>です。例を実行する前に、どちらが先に印刷されるか予測してみてください。「Large Latte」か「Fetching user order...」か。

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...');
}

前の例では、fetchUserOrder()は8行目のprint()呼び出しの前に実行されますが、コンソールには8行目の出力(「Fetching user order...」)がfetchUserOrder()の出力(「Large Latte」)の前に表示されます。これは、fetchUserOrder()が「Large Latte」を印刷する前に遅延するためです。

例: エラーによる完了

#

次の例を実行して、Futureがエラーで完了する方法を確認してください。しばらくすると、エラーの処理方法を学びます。

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...');
}

この例では、fetchUserOrder()はユーザーIDが無効であることを示すエラーで完了します。

Futureとその完了方法について学びましたが、非同期関数の結果をどのように使用しますか?次のセクションでは、asyncおよびawaitキーワードを使用して結果を取得する方法を学びます。

Futureの操作: asyncとawait

#

asyncおよびawaitキーワードは、非同期関数を宣言的に定義し、その結果を使用するための方法を提供します。asyncawaitを使用する際には、次の2つの基本的なガイドラインを覚えておいてください。

  • async関数を定義するには、関数本体の前にasyncを追加します。
  • awaitキーワードはasync関数でのみ機能します。

次の例は、main()を同期関数から非同期関数に変換する例です。

まず、関数本体の前にasyncキーワードを追加します。

dart
void main() async { ··· }

関数に宣言された戻り値の型がある場合は、その型を、関数が返す値の型であるTFuture<T>に更新します。関数が明示的に値を返さない場合は、戻り値の型はFuture<void>になります。

dart
Future<void> main() async { ··· }

これでasync関数ができたので、awaitキーワードを使用してFutureが完了するのを待つことができます。

dart
print(await createOrderMessage());

次の2つの例が示すように、asyncおよびawaitキーワードは、同期コードに非常によく似た非同期コードにつながります。唯一の違いは、非同期例で強調されています。この例は、ウィンドウが十分な幅があれば、同期例の右側に表示されます。

例: 同期関数

#
dart
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('Fetching user order...');
  print(createOrderMessage());
}
Fetching user order...
Your order is: Instance of 'Future<String>'

次の2つの例が示すように、同期コードのように動作します。

例: 非同期関数

#
dart
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('Fetching user order...');
  print(await createOrderMessage());
}
Fetching user order...
Your order is: Large Latte

非同期例は3つの点で異なります。

  • createOrderMessage()の戻り値の型がStringからFuture<String>に変更されます。
  • createOrderMessage()およびmain()の関数本体の前に、asyncキーワードが表示されます。
  • 非同期関数fetchUserOrder()およびcreateOrderMessage()の呼び出しの前に、awaitキーワードが表示されます。

asyncとawaitによる実行フロー

#

async関数は、最初のawaitキーワードまで同期的に実行されます。これは、async関数本体内で、最初のawaitキーワードより前のすべての同期コードが即座に実行されることを意味します。

例: async関数内での実行

#

次の例を実行して、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));
  }
}

前の例のコードを実行した後、2行目と3行目を逆にしてみてください。

dart
var order = await fetchUserOrder();
print('Awaiting user order...');

printOrderMessage()の最初のawaitキーワードの後にprint('Awaiting user order')が表示されるようになったため、出力のタイミングがシフトしたことに注意してください。

演習: asyncとawaitの使用練習

#

次の演習は、部分的に完了したコードスニペットを含む、失敗した単体テストです。演習を完了するには、テストをパスさせるコードを記述してください。main()を実装する必要はありません。

非同期操作をシミュレートするために、以下に提供されている関数を呼び出します。

関数型シグネチャ説明
fetchRole()Future<String> fetchRole()ユーザーの役割の簡単な説明を取得します。
fetchLoginAmount()Future<int> fetchLoginAmount()ユーザーのログイン回数を取得します。

パート1: reportUserRole()

#

reportUserRole()関数にコードを追加して、以下のことを行います。

  • 以下の文字列で完了するFutureを返します: "User role: <user role>"
    • 注: fetchRole()によって返された実際の値を使用する必要があります。例の戻り値をコピー&ペーストしてもテストはパスしません。
    • 戻り値の例: "User role: tester"
  • 提供された関数fetchRole()を呼び出すことによって、ユーザーの役割を取得します。

パート2: reportLogins()

#

以下のことを行うasync関数reportLogins()を実装します。

  • 文字列"Total number of logins: <# of logins>"を返します。
    • 注: fetchLoginAmount()によって返された実際の値を使用する必要があります。例の戻り値をコピー&ペーストしてもテストはパスしません。
    • reportLogins()からの戻り値の例: "Total number of logins: 57"
  • 提供された関数fetchLoginAmount()を呼び出すことによって、ログイン回数を取得します。
// Part 1
// Call the provided async function fetchRole()
// to return the user role.
Future<String> reportUserRole() async {
  // TODO: Implement the reportUserRole function here.
}

// Part 2
// TODO: Implement the reportLogins function here.
// Call the provided async function fetchLoginAmount()
// to return the number of times that the user has logged in.
reportLogins() {}

// The following functions those provided to you to simulate
// asynchronous operations that could take a while.

Future<String> fetchRole() => Future.delayed(_halfSecond, () => _role);
Future<int> fetchLoginAmount() => Future.delayed(_halfSecond, () => _logins);

// The following code is used to test and provide feedback on your solution.
// There is no need to read or modify it.

void main() async {
  print('Testing...');
  List<String> messages = [];
  const passed = 'PASSED';
  const testFailedMessage = 'Test failed for the function:';
  const typoMessage = 'Test failed! Check for typos in your return value';
  try {
    messages
      ..add(_makeReadable(
          testLabel: 'Part 1',
          testResult: await _asyncEquals(
            expected: 'User role: administrator',
            actual: await reportUserRole(),
            typoKeyword: _role,
          ),
          readableErrors: {
            typoMessage: typoMessage,
            'null':
                'Test failed! Did you forget to implement or return from reportUserRole?',
            'User role: Instance of \'Future<String>\'':
                '$testFailedMessage reportUserRole. Did you use the await keyword?',
            'User role: Instance of \'_Future<String>\'':
                '$testFailedMessage reportUserRole. Did you use the await keyword?',
            'User role:':
                '$testFailedMessage reportUserRole. Did you return a user role?',
            'User role: ':
                '$testFailedMessage reportUserRole. Did you return a user role?',
            'User role: tester':
                '$testFailedMessage reportUserRole. Did you invoke fetchRole to fetch the user\'s role?',
          }))
      ..add(_makeReadable(
          testLabel: 'Part 2',
          testResult: await _asyncEquals(
            expected: 'Total number of logins: 42',
            actual: await reportLogins(),
            typoKeyword: _logins.toString(),
          ),
          readableErrors: {
            typoMessage: typoMessage,
            'null':
                'Test failed! Did you forget to implement or return from reportLogins?',
            'Total number of logins: Instance of \'Future<int>\'':
                '$testFailedMessage reportLogins. Did you use the await keyword?',
            'Total number of logins: Instance of \'_Future<int>\'':
                '$testFailedMessage reportLogins. Did you use the await keyword?',
            'Total number of logins: ':
                '$testFailedMessage reportLogins. Did you return the number of logins?',
            'Total number of logins:':
                '$testFailedMessage reportLogins. Did you return the number of logins?',
            'Total number of logins: 57':
                '$testFailedMessage reportLogins. Did you invoke fetchLoginAmount to fetch the number of user logins?',
          }))
      ..removeWhere((m) => m.contains(passed))
      ..toList();

    if (messages.isEmpty) {
      print('Success. All tests passed!');
    } else {
      messages.forEach(print);
    }
  } on UnimplementedError {
    print(
        'Test failed! Did you forget to implement or return from reportUserRole?');
  } catch (e) {
    print('Tried to run solution, but received an exception: $e');
  }
}

const _role = 'administrator';
const _logins = 42;
const _halfSecond = Duration(milliseconds: 500);

// Test helpers.
String _makeReadable({
  required String testResult,
  required Map<String, String> readableErrors,
  required String testLabel,
}) {
  if (readableErrors.containsKey(testResult)) {
    var readable = readableErrors[testResult];
    return '$testLabel $readable';
  } else {
    return '$testLabel $testResult';
  }
}

// Assertions used in tests.
Future<String> _asyncEquals({
  required String expected,
  required dynamic actual,
  required String typoKeyword,
}) async {
  var strActual = actual is String ? actual : actual.toString();
  try {
    if (expected == actual) {
      return 'PASSED';
    } else if (strActual.contains(typoKeyword)) {
      return 'Test failed! Check for typos in your return value';
    } else {
      return strActual;
    }
  } catch (e) {
    return e.toString();
  }
}
ヒント

reportUserRole関数にasyncキーワードを追加するのを忘れずにいましたか?

fetchRole()を呼び出す前にawaitキーワードを使用することを忘れずにいましたか?

覚えておいてください: reportUserRoleFutureを返す必要があります。

解答
dart
Future<String> reportUserRole() async {
  final username = await fetchRole();
  return 'User role: $username';
}

Future<String> reportLogins() async {
  final logins = await fetchLoginAmount();
  return 'Total number of logins: $logins';
}

エラー処理

#

async関数でエラーを処理するには、try-catchを使用します。

dart
try {
  print('Awaiting user order...');
  var order = await fetchUserOrder();
} catch (err) {
  print('Caught error: $err');
}

async関数内では、同期コードで記述するのと同様の方法でtry-catch句を記述できます。

例: asyncとawaitとtry-catch

#

次の例を実行して、非同期関数のエラーを処理する方法を確認してください。出力は何になると思いますか?

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();
}

演習: エラー処理の練習

#

次の演習では、前のセクションで説明したアプローチを使用して、非同期コードでのエラー処理を練習します。非同期操作をシミュレートするために、コードは提供されている次の関数を呼び出します。

関数型シグネチャ説明
fetchNewUsername()Future<String> fetchNewUsername()古いユーザー名と置き換えることができる新しいユーザー名が返されます。

asyncawaitを使用して、以下のことを行う非同期関数changeUsername()を実装します。

  • 提供された非同期関数fetchNewUsername()を呼び出し、その結果を返します。
    • changeUsername()からの戻り値の例: "jane_smith_92"
  • 発生したエラーをキャッチし、エラーの文字列値を返します。
    • toString()メソッドを使用して、ExceptionErrorの両方を文字列化できます。
// TODO: Implement changeUsername here.
changeUsername() {}

// The following function is provided to you to simulate
// an asynchronous operation that could take a while and
// potentially throw an exception.

Future<String> fetchNewUsername() =>
    Future.delayed(const Duration(milliseconds: 500), () => throw UserError());

class UserError implements Exception {
  @override
  String toString() => 'New username is invalid';
}

// The following code is used to test and provide feedback on your solution.
// There is no need to read or modify it.

void main() async {
  final List<String> messages = [];
  const typoMessage = 'Test failed! Check for typos in your return value';

  print('Testing...');
  try {
    messages
      ..add(_makeReadable(
          testLabel: '',
          testResult: await _asyncDidCatchException(changeUsername),
          readableErrors: {
            typoMessage: typoMessage,
            _noCatch:
                'Did you remember to call fetchNewUsername within a try/catch block?',
          }))
      ..add(_makeReadable(
          testLabel: '',
          testResult: await _asyncErrorEquals(changeUsername),
          readableErrors: {
            typoMessage: typoMessage,
            _noCatch:
                'Did you remember to call fetchNewUsername within a try/catch block?',
          }))
      ..removeWhere((m) => m.contains(_passed))
      ..toList();

    if (messages.isEmpty) {
      print('Success. All tests passed!');
    } else {
      messages.forEach(print);
    }
  } catch (e) {
    print('Tried to run solution, but received an exception: $e');
  }
}

// Test helpers.
String _makeReadable({
  required String testResult,
  required Map<String, String> readableErrors,
  required String testLabel,
}) {
  if (readableErrors.containsKey(testResult)) {
    final readable = readableErrors[testResult];
    return '$testLabel $readable';
  } else {
    return '$testLabel $testResult';
  }
}

Future<String> _asyncErrorEquals(Function fn) async {
  final result = await fn();
  if (result == UserError().toString()) {
    return _passed;
  } else {
    return 'Test failed! Did you stringify and return the caught error?';
  }
}

Future<String> _asyncDidCatchException(Function fn) async {
  var caught = true;
  try {
    await fn();
  } on UserError catch (_) {
    caught = false;
  }

  if (caught == false) {
    return _noCatch;
  } else {
    return _passed;
  }
}

const _passed = 'PASSED';
const _noCatch = 'NO_CATCH';
ヒント

changeUsernameを実装して、fetchNewUsernameからの文字列、または失敗した場合は発生したエラーの文字列値を返します。

覚えておいてください: try-catchステートメントを使用して、エラーをキャッチして処理できます。

解答
dart
Future<String> changeUsername() async {
  try {
    return await fetchNewUsername();
  } catch (err) {
    return err.toString();
  }
}

演習: まとめ

#

最後の演習で、学んだことを実践する時が来ました。非同期操作をシミュレートするために、この演習では非同期関数fetchUsername()logoutUser()を提供します。

関数型シグネチャ説明
fetchUsername()Future<String> fetchUsername()現在のユーザーに関連付けられた名前を返します。
logoutUser()Future<String> logoutUser()現在のユーザーのログアウトを実行し、ログアウトされたユーザー名を返します。

以下を記述します。

パート1: addHello()

#
  • 単一のString引数を受け取る関数addHello()を記述します。
  • addHello()は、引数であるStringの前に'Hello 'を付けて返します。
    例: addHello('Jon')'Hello Jon' を返します。

パート2: greetUser()

#
  • 引数を取らない関数greetUser()を記述します。
  • ユーザー名を取得するために、greetUser()は提供された非同期関数fetchUsername()を呼び出します。
  • greetUser()は、addHello()を呼び出し、ユーザー名を渡して結果を返すことで、ユーザーの挨拶を作成します。
    例: fetchUsername()'Jenny'を返した場合、greetUser()'Hello Jenny'を返します。

パート3: sayGoodbye()

#
  • 以下のことを行う関数sayGoodbye()を記述します。
    • 引数を取らない。
    • エラーをキャッチする。
    • 提供された非同期関数logoutUser()を呼び出す。
  • logoutUser()が失敗した場合、sayGoodbye()は好きな文字列を返します。
  • logoutUser()が成功した場合、sayGoodbye()は文字列'<result> Thanks, see you next time'を返します。ここで、<result>logoutUser()の呼び出しによって返される文字列値です。
// Part 1
addHello(String user) {}

// Part 2
// Call the provided async function fetchUsername()
// to return the username.
greetUser() {}

// Part 3
// Call the provided async function logoutUser()
// to log out the user.
sayGoodbye() {}

// The following functions are provided to you to use in your solutions.

Future<String> fetchUsername() => Future.delayed(_halfSecond, () => 'Jean');

Future<String> logoutUser() => Future.delayed(_halfSecond, _failOnce);

// The following code is used to test and provide feedback on your solution.
// There is no need to read or modify it.

void main() async {
  const didNotImplement =
      'Test failed! Did you forget to implement or return from';

  final List<String> messages = [];

  print('Testing...');
  try {
    messages
      ..add(_makeReadable(
          testLabel: 'Part 1',
          testResult: await _asyncEquals(
              expected: 'Hello Jerry',
              actual: addHello('Jerry'),
              typoKeyword: 'Jerry'),
          readableErrors: {
            _typoMessage: _typoMessage,
            'null': '$didNotImplement addHello?',
            'Hello Instance of \'Future<String>\'':
                'Looks like you forgot to use the \'await\' keyword!',
            'Hello Instance of \'_Future<String>\'':
                'Looks like you forgot to use the \'await\' keyword!',
          }))
      ..add(_makeReadable(
          testLabel: 'Part 2',
          testResult: await _asyncEquals(
              expected: 'Hello Jean',
              actual: await greetUser(),
              typoKeyword: 'Jean'),
          readableErrors: {
            _typoMessage: _typoMessage,
            'null': '$didNotImplement greetUser?',
            'HelloJean':
                'Looks like you forgot the space between \'Hello\' and \'Jean\'',
            'Hello Instance of \'Future<String>\'':
                'Looks like you forgot to use the \'await\' keyword!',
            'Hello Instance of \'_Future<String>\'':
                'Looks like you forgot to use the \'await\' keyword!',
            '{Closure: (String) => dynamic from Function \'addHello\': static.(await fetchUsername())}':
                'Did you place the \'\$\' character correctly?',
            '{Closure \'addHello\'(await fetchUsername())}':
                'Did you place the \'\$\' character correctly?',
          }))
      ..add(_makeReadable(
          testLabel: 'Part 3',
          testResult: await _asyncDidCatchException(sayGoodbye),
          readableErrors: {
            _typoMessage:
                '$_typoMessage. Did you add the text \'Thanks, see you next time\'?',
            'null': '$didNotImplement sayGoodbye?',
            _noCatch:
                'Did you remember to call logoutUser within a try/catch block?',
            'Instance of \'Future<String>\' Thanks, see you next time':
                'Did you remember to use the \'await\' keyword in the sayGoodbye function?',
            'Instance of \'_Future<String>\' Thanks, see you next time':
                'Did you remember to use the \'await\' keyword in the sayGoodbye function?',
          }))
      ..add(_makeReadable(
          testLabel: 'Part 3',
          testResult: await _asyncEquals(
              expected: 'Success! Thanks, see you next time',
              actual: await sayGoodbye(),
              typoKeyword: 'Success'),
          readableErrors: {
            _typoMessage:
                '$_typoMessage. Did you add the text \'Thanks, see you next time\'?',
            'null': '$didNotImplement sayGoodbye?',
            _noCatch:
                'Did you remember to call logoutUser within a try/catch block?',
            'Instance of \'Future<String>\' Thanks, see you next time':
                'Did you remember to use the \'await\' keyword in the sayGoodbye function?',
            'Instance of \'_Future<String>\' Thanks, see you next time':
                'Did you remember to use the \'await\' keyword in the sayGoodbye function?',
            'Instance of \'_Exception\'':
                'CAUGHT Did you remember to return a string?',
          }))
      ..removeWhere((m) => m.contains(_passed))
      ..toList();

    if (messages.isEmpty) {
      print('Success. All tests passed!');
    } else {
      messages.forEach(print);
    }
  } catch (e) {
    print('Tried to run solution, but received an exception: $e');
  }
}

// Test helpers.
String _makeReadable({
  required String testResult,
  required Map<String, String> readableErrors,
  required String testLabel,
}) {
  String? readable;
  if (readableErrors.containsKey(testResult)) {
    readable = readableErrors[testResult];
    return '$testLabel $readable';
  } else if ((testResult != _passed) && (testResult.length < 18)) {
    readable = _typoMessage;
    return '$testLabel $readable';
  } else {
    return '$testLabel $testResult';
  }
}

Future<String> _asyncEquals({
  required String expected,
  required dynamic actual,
  required String typoKeyword,
}) async {
  final strActual = actual is String ? actual : actual.toString();
  try {
    if (expected == actual) {
      return _passed;
    } else if (strActual.contains(typoKeyword)) {
      return _typoMessage;
    } else {
      return strActual;
    }
  } catch (e) {
    return e.toString();
  }
}

Future<String> _asyncDidCatchException(Function fn) async {
  var caught = true;
  try {
    await fn();
  } on Exception catch (_) {
    caught = false;
  }

  if (caught == true) {
    return _passed;
  } else {
    return _noCatch;
  }
}

const _typoMessage = 'Test failed! Check for typos in your return value';
const _passed = 'PASSED';
const _noCatch = 'NO_CATCH';
const _halfSecond = Duration(milliseconds: 500);

String _failOnce() {
  if (_logoutSucceeds) {
    return 'Success!';
  } else {
    _logoutSucceeds = true;
    throw Exception('Logout failed');
  }
}

bool _logoutSucceeds = false;
ヒント

greetUser関数とsayGoodbye関数は非同期である必要がありますが、addHelloは通常の同期関数である必要があります。

覚えておいてください: try-catchステートメントを使用して、エラーをキャッチして処理できます。

解答
dart
String addHello(String user) => 'Hello $user';

Future<String> greetUser() async {
  final username = await fetchUsername();
  return addHello(username);
}

Future<String> sayGoodbye() async {
  try {
    final result = await logoutUser();
    return '$result Thanks, see you next time';
  } catch (e) {
    return 'Failed to logout user: $e';
  }
}

Futureに有効なLintは?

#

asyncとFutureの操作で発生する一般的な間違いをキャッチするために、以下のLintを有効にしてください。

次は?

#

おめでとうございます、チュートリアルは終了です!さらに学習したい場合は、次の場所から始めることをお勧めします。