跳到主內容

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

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

為了充分利用本教程,你應該具備以下條件:

本教程涵蓋以下內容:

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

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

本教程中的練習包含部分完成的程式碼片段。你可以使用 DartPad 透過完成程式碼並點選 Run 按鈕來測試你的知識。請勿編輯 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() 是一個非同步函式,它在延遲後提供一個描述使用者訂單的字串:“大杯拿鐵”。
  • 為了獲取使用者訂單,createOrderMessage() 應該呼叫 fetchUserOrder() 並等待其完成。由於 createOrderMessage() *沒有* 等待 fetchUserOrder() 完成,因此 createOrderMessage() 未能獲取 fetchUserOrder() 最終提供的字串值。
  • 相反,createOrderMessage() 獲取了一個待完成工作的表示:一個未完成的 Future。你將在下一節中瞭解更多關於 Future 的資訊。
  • 由於 createOrderMessage() 未能獲取描述使用者訂單的值,此示例未能將“大杯拿鐵”列印到控制檯,而是列印了“Your order is: Instance of '_Future<String>'"。

在接下來的章節中,你將瞭解 Future 以及如何使用 Future(使用 asyncawait),以便你能夠編寫必要的程式碼,使 fetchUserOrder() 將所需的值(“大杯拿鐵”)列印到控制檯。

什麼是 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>。在執行示例之前,嘗試預測哪個會先列印:“大杯拿鐵”還是“正在獲取使用者訂單...”?

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 行的輸出(“正在獲取使用者訂單...”),而不是 fetchUserOrder() 的輸出(“大杯拿鐵”)。這是因為 fetchUserOrder() 在列印“大杯拿鐵”之前有延遲。

示例:帶錯誤的完成

#

執行以下示例,檢視 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
  • 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,它以以下字串完成:"使用者角色: <使用者角色>"
    • 注意:你必須使用 fetchRole() 返回的實際值;複製貼上示例返回值不會使測試透過。
    • 示例返回值:"使用者角色: tester"
  • 透過呼叫提供的函式 fetchRole() 獲取使用者角色。

第 2 部分:reportLogins()

#

實現一個 async 函式 reportLogins(),使其執行以下操作:

  • 返回字串 `"登入總數: <登入次數>"`。
    • 注意:你必須使用 fetchLoginAmount() 返回的實際值;複製貼上示例返回值不會使測試透過。
    • reportLogins() 的示例返回值:"登入總數: 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 子句

示例: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"
  • 捕獲發生的任何錯誤並返回錯誤的字串值。
// 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() 返回字串 `'<結果> 謝謝,下次再見'`,其中 <結果> 是呼叫 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';
  }
}

哪些 Lint 規則適用於 Future?

#

為了捕獲使用 async 和 Future 時出現的常見錯誤,請啟用以下 Lint 規則:

接下來是什麼?

#

恭喜,你已完成本教程!如果你想了解更多資訊,以下是一些建議: