目次

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

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

このチュートリアルを最大限に活用するには、次のものが必要です。

このチュートリアルでは、次の内容について説明します。

  • `async`と`await`キーワードの使用方法と使用タイミング。
  • `async`と`await`の使用が実行順序に与える影響。
  • `async`関数内で`try-catch`式を使用して、非同期呼び出しからのエラーを処理する方法。

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

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

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

非同期コードの重要性

#

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

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

このような非同期計算は、通常、結果を`Future`として(結果が複数の部分を持つ場合は`Stream`として)提供します。これらの計算は、プログラムに非同期性を導入します。その初期の非同期性を考慮するために、他のプレーンなDart関数も非同期になる必要があります。

これらの非同期結果を操作するには、`async`と`await`キーワードを使用できます。ほとんどの非同期関数は、最終的には本質的に非同期的な計算に依存する、単なる非同期Dart関数です。

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

#

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

// 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'」を印刷します。

次のセクションでは、FutureとFutureの操作(`async`と`await`の使用)について学習し、`fetchUserOrder()`に必要なコードを記述して、コンソールに目的の値(「Large Latte」)を出力できるようにします。

Futureとは?

#

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

未完了

#

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

完了

#

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

値での完了

#

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

エラーでの完了

#

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

例:Futureの導入

#

次の例では、`fetchUserOrder()`はコンソールに出力した後に完了するFutureを返します。使用可能な値を返さないため、`fetchUserOrder()`の型は`Future`です。例を実行する前に、「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` キーワードは、非同期関数を定義し、その結果を使用するための宣言的な方法を提供します。`async` と `await` を使用する場合、次の2つの基本的なガイドラインを覚えておいてください。

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

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

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

dart
void main() async { ··· }

関数が宣言された戻り値の型を持つ場合、その型を `Future` に更新します。ここで、`T` は関数が返す値の型です。関数が明示的に値を返さない場合、戻り値の型は `Future` になります。

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` に変更されます。
  • `async` キーワードが `createOrderMessage()` と `main()` の関数本体の前に表示されます。
  • `await` キーワードが、非同期関数 `fetchUserOrder()` と `createOrderMessage()` を呼び出す前に表示されます。

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

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

演習:asyncとawaitの使用練習

#

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

非同期操作をシミュレートするには、次の関数(提供されています)を呼び出します。

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

パート1: `reportUserRole()`

#

`reportUserRole()` 関数に次のことを行うコードを追加します。

  • 次の文字列で完了するFutureを返します。「User role: <ユーザーの役割>」
    • 注: `fetchRole()` が返す実際の値を使用する必要があります。例の結果値をコピーして貼り付けても、テストは合格しません。
    • 例の結果値: 「User role: tester」
  • 提供された関数 `fetchRole()` を呼び出してユーザーの役割を取得します。

パート2: `reportLogins()`

#

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

  • 文字列「Total number 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` キーワードを使用しましたか?

覚えておいてください: `reportUserRole` は `Future` を返す必要があります。

解決策
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 節 を記述できます。

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

演習:エラー処理の練習

#

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

関数型シグネチャ説明
fetchNewUsername()Future fetchNewUsername()古いユーザー名に代えて使用できる新しいユーザー名を返します。

`async` と `await` を使用して、次のことを行う非同期 `changeUsername()` 関数を実装します。

  • 提供された非同期関数 `fetchNewUsername()` を呼び出して、その結果を返します。
    • `changeUsername()` からの例の結果値: 「jane_smith_92」
  • 発生したエラーをキャッチし、エラーの文字列値を返します。
// 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';
ヒント

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

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

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

演習:すべてをまとめる

#

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

関数型シグネチャ説明
fetchUsername()Future fetchUsername()現在のユーザーに関連付けられた名前を返します。
logoutUser()Future 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()` は文字列 `<結果> Thanks, see you next time` を返します。ここで、`<結果>` は `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 と futures を使用して作業中に発生する一般的な間違いをキャッチするために、次のlintを有効にします

次のステップ

#

チュートリアルを完了しました。さらに学習したい場合は、次に進む場所に関する提案をいくつか示します。