Dart 所有例外皆為非檢查型例外,函數不需宣告所拋出的錯誤類型,也不會強制開發者進行捕捉
分為兩大類:
Exception:業務邏輯可預期錯誤(格式錯誤、參數異常等,建議捕捉並處理)Error:程式致命錯誤(型別錯誤、空指標、未實作方法等,通常不建議捕捉)
任何非 null 的物件都能透過 throw 拋出,正式專案規範僅允許拋出 Exception/Error 的子類別
未被捕捉的例外會直接終止當前隔離執行緒(isolate),造成程式崩潰
throw 拋出例外
1. 拋出系統內建例外
void main() {
// 格式例外
throw FormatException("至少需要1段資料");
}
Code language: Dart (dart)
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
FormatException: 錯誤描述
#0 main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:3:3)
#1 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#2 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)
上面這段程式會手動拋出自訂描述的系統例外,執行後就會印出錯誤。你可能會覺得這段程式沒什麼實用價值,但拋出例外是開發上非常重要的機制。舉例使用者註冊場景:我們要驗證使用者輸入格式是否合法,一旦格式不符就直接拋出例外提示使用者,同時中斷流程,避免不合法的髒資料流入,維持資料完整性。
2. 拋出任意物件
正式上線專案不推薦此寫法
void main() {
throw "文字描述!";
}Code language: Dart (dart)
執行結果
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
文字描述!
#0 main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:2:3)
#1 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#2 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)
3. 箭頭函式內使用 throw(throw 屬於運算式)
class Point {}
void distanceTo(Point other) => throw UnimplementedError("該方法尚未實作");
void main() {
distanceTo(Point());
}
Code language: JavaScript (javascript)
三、try /on/catch 捕捉例外
語法差異說明
on 例外類型:只匹配指定類型的例外,無法取得例外物件實體catch(e):捕捉所有類型例外,變數 e 存放例外實例catch(e, s):e = 例外物件,s = 呼叫堆疊資訊 StackTrace
1:使用 on 單一指定類型捕捉
//自訂例外類別,實作 Exception
class OutOfLlamasException implements Exception {}
//拋出自訂例外的函式
void breedMoreLlamas() {
throw OutOfLlamasException();
}
void buyMoreLlamas() {
print("執行了buyMoreLlamas");
}
void main() {
try {
breedMoreLlamas(); //呼叫執行有風險的程式
} on OutOfLlamasException {
buyMoreLlamas(); //若捕捉到 OutOfLlamasException 就執行這段邏輯
}
}Code language: Dart (dart)
執行結果
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
執行了buyMoreLlamasCode language: CSS (css)
try {
//預期會出錯的程式
} on 例外類別 {
//捕捉到錯誤後要執行的邏輯
}Code language: Dart (dart)
try on 就是專門用來依類型捕捉例外的語法。
2:多層分支分級捕捉
精準匹配優先執行
class OutOfLlamasException implements Exception {}
void breedMoreLlamas() {
throw FormatException("資料格式錯誤");
}
void main() {
try {
breedMoreLlamas();
}
// 1. 精準匹配自訂例外
on OutOfLlamasException {
print("OutOfLlamasException 發生錯誤");
}
// 2. 匹配所有 Exception 子類別
on Exception catch (e) {
print("一般業務例外:$e");
}
// 3. 兜底捕捉 Error 與其他任意拋出物件
catch (e) {
print("未知嚴重錯誤:$e");
}
}
Code language: Dart (dart)
輸出 : 一般業務例外:FormatException: 資料格式錯誤
我們可以透過多個 on 區塊分開捕捉特定 Exception,最後搭配 catch 做全域兜底。
3:捕捉例外並印出完整呼叫堆疊 StackTrace
void main() {
try {
int a = null;
print(a + 1);
} catch (e, stack) {
print("例外資訊:$e");
print("完整呼叫堆疊:\n$stack");
}
}
Code language: Dart (dart)
這段程式除了印出錯誤提示,還會輸出完整的函數呼叫鏈,方便除錯定位問題。
D:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:3:13: Error: A value of type 'Null' can't be assigned to a variable of type 'int'.
int a = null;Code language: PHP (php)
四、rethrow 重新拋出
局部處理部分邏輯後,將例外繼續向上層傳遞
用途:當前函式先處理一部分復原邏輯,再把例外交給上一層呼叫者繼續處理
有時我們不能只在當前函式處理完錯誤,還需要把例外往上拋,讓呼叫此函式的上層程式也同步處理。
void misbehave() {
try {
dynamic foo = true;
print(foo++); // true 無法執行遞增,執行期觸發錯誤
} catch (e) {
print("misbehave 局部處理:${e.runtimeType}");
rethrow; // 重新拋出,交給外層 try 捕捉
}
}
void main() {
try {
misbehave();
} catch (e) {
print("main 最終處理:${e.runtimeType}");
}
}
Code language: Dart (dart)
執行流程:
misbehave 局部處理:NoSuchMethodError
main 最終處理:NoSuchMethodError
範例中的例外會同時在 misbehave 與 main 兩個區塊分別處理。
五、finally 最終區塊
不論程式是否發生錯誤,一定會執行
兩種常見使用場景:
- 僅搭配 try + finally:例外不會被捕捉,執行完 finally 邏輯後程式直接崩潰
- try + catch + finally:先處理例外,最後執行 finally(專門用於釋放資源)
1:try + finally
void cleanLlamaStalls() => print("清理佔用資源,無論如何必定執行");
void breedMoreLlamas() => throw Exception("程式發生錯誤");
void main() {
try {
breedMoreLlamas();
} finally {
cleanLlamaStalls();
}
}
Code language: Dart (dart)
執行結果:雖然 finally 的清理程式會執行,但未被捕捉的例外依舊會造成程式崩潰。
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
清理佔用資源,無論如何必定執行
Unhandled exception:
Exception: 程式發生錯誤
#0 breedMoreLlamas (file:///D:/dartdemo/firstdart/bin/firstdart.dart:2:27)
#1 main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:5)
#2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#3 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)
2:try + catch + finally
釋放資源的標準寫法,例如開啟檔案後必須關閉釋放
void cleanLlamaStalls() => print("關閉檔案、釋放系統資源");
void breedMoreLlamas() => throw FormatException("資料解析失敗");
void main() {
try {
breedMoreLlamas();
} catch (e) {
print("捕捉到例外:$e");
} finally {
cleanLlamaStalls();
}
}
Code language: Dart (dart)
執行順序:觸發例外 → catch 處理錯誤 → 執行 finally
執行結果
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
捕捉到例外:FormatException: 資料解析失敗
關閉檔案、釋放系統資源
Code language: CSS (css)
六、assert 斷言
僅開發除錯階段生效,正式發布版本會直接失效
assert(布林判斷條件, [可選自訂錯誤提示文字])
當判斷條件為 false 時,會拋出 AssertionError;正式編譯後會完全忽略斷言,不會產生任何效能損耗。
1:基礎斷言範例
void main() {
String? text;
int number = 120;
String url = "http://foxdevelop.com";
assert(text != null); // 空值檢查
assert(number < 100); // 數值範圍驗證
assert(url.startsWith("https")); // 網址協定檢查
}
Code language: JavaScript (javascript)
直接執行這段程式,畫面上不會輸出任何內容!
原因是斷言預設為關閉狀態,必須手動帶參數開啟才會生效,繼續往下閱讀開啟方式。
2:自訂斷言失敗提示文字
void main() {
String url = "http://test.com";
assert(
url.startsWith("https"),
"連結 $url 必須以 https 開頭",
);
}
Code language: JavaScript (javascript)
我們可以自行定義斷言失敗時顯示的錯誤訊息。
斷言開啟方式
- Flutter Debug 偵錯模式預設自動開啟斷言
- dart 指令執行:
dart --enable-asserts 你的dart檔名.dart - 正式打包編譯:所有斷言程式碼會被移除,無額外效能耗損
D:\dartdemo\firstdart\bin>dart --enable-asserts firstdart.dart
Unhandled exception:
'file:///D:/dartdemo/firstdart/bin/firstdart.dart': Failed assertion: line 6 pos 10: 'text != null': is not true.
#0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:67:4)
#1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:49:5)
#2 main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:10)
#3 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#4 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)
dart run 與單純 dart 指令預設都會關閉斷言,必須追加 --enable-asserts 參數:
執行指令範例
dart run --enable-asserts main.dartCode language: CSS (css)
若終端目前位於專案根目錄,可直接簡寫:
執行指令:
dart run --enable-asserts
dart run:直接執行 Dart 原始碼,適合開發階段測試
dart 檔名:簡化單檔執行語法
dart compile exe:編譯獨立執行檔,編譯成品永遠停用 assert
Dart 程式存放於專案內的 bin 資料夾,你可以切進 bin 資料夾執行單一檔案,或是切回上一層專案根目錄執行整個專案。
dart run –enable-asserts
下方示範兩種執行方式:直接在 bin 內執行,與回到專案根目錄執行。
D:\dartdemo\firstdart\bin>dart --enable-asserts firstdart.dart
Unhandled exception:
'file:///D:/dartdemo/firstdart/bin/firstdart.dart': Failed assertion: line 6 pos 10: 'text != null': is not true.
#0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:67:4)
#1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:49:5)
#2 main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:10)
#3 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#4 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)
D:\dartdemo\firstdart\bin>dart run
Building package executable...
Built firstdart:firstdart.
D:\dartdemo\firstdart\bin>cd ..
D:\dartdemo\firstdart>dart run --enable-asserts
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
'file:///D:/dartdemo/firstdart/bin/firstdart.dart': Failed assertion: line 6 pos 10: 'text != null': is not true.
#0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:67:4)
#1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:49:5)
#2 main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:10)
#3 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#4 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)
若斷言判斷條件為 true,則不會拋出任何錯誤
修改範例程式:將網址的 http 改為 https、text != null 改成 text == null、number 數值改為 99
void main() {
String? text;
int number = 99;
String url = "https://foxdevelop.com";
assert(text == null); // 空值檢查
assert(number < 100); // 數值範圍驗證
assert(url.startsWith("https")); // 網址協定檢查
}Code language: Dart (dart)
再次執行就不會觸發任何例外
D:\dartdemo\firstdart>dart run --enable-asserts
Building package executable...
Built firstdart:firstdart.Code language: CSS (css)
七、綜合完整範例
void cleanResource() => print("finally:釋放佔用資源");
void testError() {
try {
int num = 150;
assert(num < 100, "數字必須小於100");
throw FormatException("資料格式非法");
} on FormatException catch (e) {
print("捕捉格式例外:$e");
rethrow;
} finally {
cleanResource();
}
}
void main() {
try {
testError();
} catch (e) {
print("main 捕捉二次拋出的例外:$e");
}
}
Code language: PHP (php)
執行結果
D:\dartdemo\firstdart>dart run --enable-asserts
Building package executable...
Built firstdart:firstdart.
finally:釋放佔用資源
main 捕捉二次拋出的例外:'file:///D:/dartdemo/firstdart/bin/firstdart.dart':
Failed assertion: line 6 pos 12: 'num < 100': 數字必須小於100Code language: PHP (php)
完整例外錯誤處理示範