非同步程式設計:futures、async、await
此 Codelab 教您如何使用 futures 以及 async
和 await
關鍵字撰寫非同步程式碼。透過內嵌的 DartPad 編輯器,您可以執行範例程式碼和完成練習,來測試您的知識。
若要充分利用此 Codelab,您應該具備下列條件
- 了解 Dart 基本語法。
- 具備使用其他語言撰寫非同步程式碼的一些經驗。
此 Codelab 涵蓋下列教材
- 如何以及何時使用
async
和await
關鍵字。 - 使用
async
和await
如何影響執行順序。 - 如何使用
async
函式中的try-catch
表達式來處理非同步呼叫的錯誤。
完成此 Codelab 的預計時間:40-60 分鐘。
此 Codelab 中的練習包含部分完成的程式碼片段。您可以使用 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<String>'」。
在下一節中,你將了解 future 以及如何使用 future(使用 async
和 await
),以便能夠撰寫必要的程式碼,讓 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 無效。
您已經瞭解未來以及它們如何完成,但您要如何使用非同步函式的結果?在下一節中,您將會學習如何使用 async
和 await
關鍵字取得結果。
使用 future:async 和 await
#async
和 await
關鍵字提供宣告方式來定義非同步函式並使用其結果。使用 async
和 await
時,請記住這兩個基本準則
- 若要定義非同步函式,請在函式主體前加上
async
await
關鍵字僅在async
函式中運作。
以下是一個範例,將 main()
從同步函式轉換為非同步函式。
首先,在函式主體前加上 async
關鍵字
void main() async { ··· }
如果函式有宣告的傳回類型,則將類型更新為 Future<T>
,其中 T
是函式傳回值的類型。如果函式沒有明確傳回值,則傳回類型為 Future<void>
Future<void> main() async { ··· }
現在您有一個 async
函式,您可以使用 await
關鍵字來等待未來完成
print(await createOrderMessage());
如下兩個範例所示,async
和 await
關鍵字會產生非同步程式碼,其看起來非常像同步程式碼。唯一的差異會在非同步範例中標示出來,如果您的視窗夠寬,它會在同步範例的右側。
範例:同步函式
#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>'
範例:非同步函式
#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 行反轉
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
。
解答
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
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() | 傳回您可以用來取代舊名稱的新使用者名稱。 |
使用 async
和 await
實作非同步 changeUsername()
函式,此函式執行下列動作
- 呼叫提供的非同步函式
fetchNewUsername()
並傳回其結果。changeUsername()
的範例傳回值:"jane_smith_92"
- 捕捉發生的任何錯誤並傳回錯誤的字串值。
- 您可以使用 toString() 方法將 Exceptions 和 Errors. 字串化。
// 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 陳述式 來捕捉和處理錯誤。
解答
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
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!如果您想進一步了解,以下是接下來可以前往的建議
- 使用 DartPad。
- 嘗試另一個 codelab。
- 進一步了解 Dart 中的 future 和非同步程式碼
- 串流教學課程:了解如何處理一系列非同步事件。
- Dart 中的並行處理:了解並學習如何在 Dart 中實作並行處理。
- 非同步支援:深入了解 Dart 的語言和函式庫,以支援非同步編碼。
- Google 的 Dart 影片:觀看一部或多部關於非同步編碼的影片。
- 取得 Dart SDK!