跳到主要內容

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

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

為了充分利用本教學課程,您應該具備以下條件

本教學課程涵蓋以下內容

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

完成本教學課程的預計時間:40-60 分鐘。

本教學課程中的練習包含部分完成的程式碼片段。您可以使用 DartPad 完成程式碼並點擊「執行」按鈕來測試您的知識。請勿編輯 main 函式或以下的測試程式碼。

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

為何非同步程式碼很重要

#

非同步操作可讓您的程式在等待另一個操作完成時繼續執行工作。以下是一些常見的非同步操作

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

此類非同步計算通常會將其結果以 FutureStream(如果結果有多個部分)的形式提供。這些計算會將非同步性引入程式中。為了適應最初的非同步性,其他純 Dart 函式也需要變成非同步函式。

若要與這些非同步結果互動,您可以使用 asyncawait 關鍵字。大多數非同步函式只是 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() 是一個非同步函式,它在延遲後會提供一個描述使用者訂單的字串:「大杯拿鐵」。
  • 為了取得使用者的訂單,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。該 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 以及它們如何完成,但是您如何使用非同步函式的結果呢?在下一節中,您將學習如何使用 asyncawait 關鍵字取得結果。

使用 Future:async 與 await

#

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

  • 若要定義 async 函式,請在函式主體之前新增 async
  • await 關鍵字僅在 async 函式中運作。

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

首先,在函式主體之前新增 async 關鍵字

dart
void main() async { ··· }

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

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

現在您有了 async 函式,您可以使用 await 關鍵字等待 future 完成

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() 函式,使其執行以下操作

  • 傳回一個 future,該 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();
  }
}
提示

您是否記得將 async 關鍵字新增至 reportUserRole 函式?

您是否記得在叫用 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;
提示

greetUsersayGoodbye 函式應為非同步函式,而 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';
  }
}

哪些 lints 適用於 Future?

#

為了捕捉在使用 async 和 Future 時出現的常見錯誤,請啟用以下 lints

下一步?

#

恭喜,您已完成本教學課程!如果您想瞭解更多資訊,以下是一些後續建議