內容

非同步程式設計:futures、async、await

此 Codelab 教您如何使用 futures 以及 asyncawait 關鍵字撰寫非同步程式碼。透過內嵌的 DartPad 編輯器,您可以執行範例程式碼和完成練習,來測試您的知識。

若要充分利用此 Codelab,您應該具備下列條件

  • 了解 Dart 基本語法
  • 具備使用其他語言撰寫非同步程式碼的一些經驗。

此 Codelab 涵蓋下列教材

  • 如何以及何時使用 asyncawait 關鍵字。
  • 使用 asyncawait 如何影響執行順序。
  • 如何使用 async 函式中的 try-catch 表達式來處理非同步呼叫的錯誤。

完成此 Codelab 的預計時間:40-60 分鐘。

此 Codelab 中的練習包含部分完成的程式碼片段。您可以使用 DartPad 透過完成程式碼並按一下執行按鈕,來測試您的知識。請勿編輯 main 函式或其後方的測試程式碼

如果您需要協助,請在每個練習後展開提示解答下拉式選單。

非同步程式碼為何重要

#

非同步作業讓您的程式可以在等待另一個作業完成時完成工作。以下是常見的非同步作業

  • 透過網路擷取資料。
  • 寫入資料庫。
  • 從檔案讀取資料。

此類非同步運算通常會提供其結果作為 Future,或者如果結果有多個部分,則作為 Stream。這些運算會在程式中引入非同步性。為了適應這種初始非同步性,其他純粹的 Dart 函式也需要變成非同步。

要與這些非同步結果互動,你可以使用 asyncawait 關鍵字。大多數非同步函式只是非同步 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 代表非同步作業的結果,可以有兩種狀態:未完成或已完成。

未完成

#

當您呼叫非同步函式時,它會傳回未完成的未來。該未來正在等待函式的非同步作業完成或擲回錯誤。

已完成

#

如果非同步作業成功,未來會以值完成。否則,它會以錯誤完成。

以值完成

#

Future<T> 類型的未來會以 T 類型的值完成。例如,類型為 Future<String> 的未來會產生字串值。如果未來不會產生可用值,則未來的類型為 Future<void>

以錯誤完成

#

如果函式執行的非同步作業因任何原因而失敗,未來會以錯誤完成。

範例:介紹 future

#

在以下範例中,fetchUserOrder() 會傳回一個未來,在列印至主控台後完成。由於它不會傳回可用值,因此 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() 呼叫之前執行,主控台仍會在 fetchUserOrder()(「Large Latte」)的輸出之前顯示第 8 行的輸出(「Fetching user order...」)。這是因為 fetchUserOrder() 會延遲列印「Large Latte」。

範例:以錯誤完成

#

執行以下範例,瞭解未來如何以錯誤完成。稍後您將會學習如何處理錯誤。

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 無效。

您已經瞭解未來以及它們如何完成,但您要如何使用非同步函式的結果?在下一節中,您將會學習如何使用 asyncawait 關鍵字取得結果。

使用 future:async 和 await

#

asyncawait 關鍵字提供宣告方式來定義非同步函式並使用其結果。使用 asyncawait 時,請記住這兩個基本準則

  • 若要定義非同步函式,請在函式主體前加上 async
  • await 關鍵字僅在 async 函式中運作。

以下是一個範例,將 main() 從同步函式轉換為非同步函式。

首先,在函式主體前加上 async 關鍵字

dart
void main() async { ··· }

如果函式有宣告的傳回類型,則將類型更新為 Future<T>,其中 T 是函式傳回值的類型。如果函式沒有明確傳回值,則傳回類型為 Future<void>

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

現在您有一個 async 函式,您可以使用 await 關鍵字來等待未來完成

dart
print(await createOrderMessage());

如下兩個範例所示,asyncawait 關鍵字會產生非同步程式碼,其看起來非常像同步程式碼。唯一的差異會在非同步範例中標示出來,如果您的視窗夠寬,它會在同步範例的右側。

範例:同步函式

#
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>'

範例:非同步函式

#
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

非同步範例有三個不同之處

  • createOrderMessage() 的傳回類型從 String 變更為 Future<String>
  • 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<String> fetchRole()取得使用者角色的簡短說明。
fetchLoginAmount()Future<int> fetchLoginAmount()取得使用者登入的次數。

第 1 部分:reportUserRole()

#

將程式碼新增到 reportUserRole() 函式,使其執行下列動作

  • 傳回一個未來,並完成下列字串:"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 reportUserRole 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<String> fetchNewUsername()傳回您可以用來取代舊名稱的新使用者名稱。

使用 asyncawait 實作非同步 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';
提示

實作 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()

#
  • 撰寫一個函式 addHello(),它帶有一個 String 參數。
  • 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;
Hint

The greetUser and sayGoodbye functions should be asynchronous, while addHello should be a normal, synchronous function.

Remember: You can use a try-catch statement to catch and handle errors.

Solution
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';
  }
}

下一步是什麼?

#

恭喜您,您已完成 codelab!如果您想進一步了解,以下是接下來可以前往的建議