Dart Exception Handling
try-catch-finally, on-type handling, rethrow
Dart distinguishes recoverable Exceptions from unrecoverable Errors. try-catch-finally handles exceptions, with on-clauses for type-specific branching. rethrow preserves the original stack trace while propagating upward, and async code uses try-catch or Future.catchError() for the same handling.
Exception vs Error
Dartλ Exception(볡ꡬ κ°λ₯ν μ€λ₯)κ³Ό Error(νλ‘κ·Έλλ°/μμ€ν μ€λ₯, 볡ꡬ λΆκ°)λ₯Ό ꡬλΆν©λλ€.
Exception (볡ꡬ κ°λ₯)
FormatException, StateError, TypeError, ArgumentError, RangeError, TimeoutException
Error (볡ꡬ λΆκ°)
AssertionError, NoSuchMethodError, StackOverflowError, OutOfMemoryError
κΈ°λ³Έ try-catch-finally
try λΈλ‘μμ μ€ννκ³ , catchμμ μμΈλ₯Ό μ²λ¦¬νλ©°, finallyλ νμ μ€νλ©λλ€.
try {
int result = 12 ~/ 0;
print('κ²°κ³Ό: \$result');
} catch (e) {
print('μμΈ λ°μ: \$e');
} finally {
print('finally λΈλ‘ μ€ν');
}
on μ λ‘ νμ λ³ μ²λ¦¬
on νμ
catch (e)μΌλ‘ μμΈ μ’
λ₯λ³λ‘ λΆκΈ°ν μ μμ΅λλ€. λ§μ§λ§ catch (e, s)λ λλ¨Έμ§ λͺ¨λ μμΈλ₯Ό μ‘μΌλ©° μ€ν νΈλ μ΄μ€λ λ°μ΅λλ€.
try {
dynamic value = 'not a number';
int number = int.parse(value);
} on FormatException catch (e) {
print('μ«μλ‘ λ³νν μ μμ: \$e');
} on TypeError catch (e) {
print('νμ
μ€λ₯ λ°μ: \$e');
} catch (e, s) {
print('κΈ°ν μμΈ: \$e');
print('μ€ν νΈλ μ΄μ€: \$s');
}
rethrow β μλ μ€ν νΈλ μ΄μ€ 보쑴
catchμμ λ‘κΉ
ν rethrowλ‘ μμμ μ νν©λλ€. throw eμ λ¬λ¦¬ μλ μ€ν νΈλ μ΄μ€κ° μ μ§λ©λλ€.
void processFile(String filename) {
try {
var file = File(filename);
var contents = file.readAsStringSync();
} catch (e) {
print('νμΌ μ²λ¦¬ μ€ μ€λ₯: \$e');
rethrow; // μλ μ€ν νΈλ μ΄μ€ μ μ§
}
}
void main() {
try {
processFile('μ‘΄μ¬νμ§_μλ_νμΌ.txt');
} catch (e) {
print('λ©μΈμμ μ€λ₯ μ²λ¦¬: \$e');
}
}
컀μ€ν Exception ν΄λμ€
implements ExceptionμΌλ‘ λλ©μΈλ³ μμΈλ₯Ό μ μνλ©΄ μλ―Έ μλ μλ¬ λ©μμ§μ 컨ν
μ€νΈλ₯Ό μ λ¬ν μ μμ΅λλ€.
class InsufficientBalanceException implements Exception {
final double balance;
final double withdrawal;
InsufficientBalanceException(this.balance, this.withdrawal);
@override
String toString() {
return 'μμ‘ λΆμ‘±: νμ¬ \$balance, μΆκΈ μμ² \$withdrawal';
}
}
// μ¬μ©
try {
account.withdraw(1500);
} on InsufficientBalanceException catch (e) {
print('μΆκΈ μ€ν¨: \$e');
} on ArgumentError catch (e) {
print('μΈμ μ€λ₯: \$e');
}
λΉλκΈ° μμΈ β async/await
async ν¨μμμλ λκΈ° μ½λμ λμΌνκ² try-catch-finallyλ₯Ό μ¬μ©ν©λλ€.
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 1));
throw Exception('λ°μ΄ν°λ₯Ό κ°μ Έμ¬ μ μμ΅λλ€.');
}
Future<void> processData() async {
try {
String data = await fetchData();
print('λ°μ΄ν°: \$data');
} catch (e) {
print('μ€λ₯ λ°μ: \$e');
} finally {
print('λ°μ΄ν° μ²λ¦¬ μλ£');
}
}
λΉλκΈ° μμΈ β Future 체μΈ
Future 체μΈμμλ .catchError()μ .whenComplete()λ₯Ό μ¬μ©ν©λλ€. test: νλΌλ―Έν°λ‘ νΉμ νμ
λ§ μ νμ μΌλ‘ μ²λ¦¬ν μλ μμ΅λλ€.
fetchData()
.then((data) => print('λ°μ΄ν°: \$data'))
.catchError((e) => print('μ€λ₯ λ°μ: \$e'))
.whenComplete(() => print('μμ
μλ£'));
// νΉμ νμ
λ§ μ ν μ²λ¦¬
processTask()
.catchError(
(e) => print('νμμμ: \$e'),
test: (e) => e is TimeoutException,
)
.catchError((e) => print('κΈ°ν μ€λ₯: \$e'));
Stream μμΈ μ²λ¦¬
Streamμ 3κ°μ§ λ°©μμΌλ‘ μμΈλ₯Ό μ²λ¦¬ν μ μμ΅λλ€.
1. await for + try-catch
try {
await for (var number in countStream(5)) {
print('μ«μ: \$number');
}
} catch (e) {
print('μ€νΈλ¦Ό μ€λ₯: \$e');
}
2. listenμ onError μ½λ°±
countStream(5).listen(
(data) => print('μ«μ: \$data'),
onError: (e) => print('μ€λ₯: \$e'),
onDone: () => print('μλ£'),
cancelOnError: false, // falseλ©΄ μ€λ₯ νμλ κ³μ
);
3. handleError λ©μλ
generateNumbers()
.handleError((error) => print('μ²λ¦¬: \$error'))
.listen((data) => print('λ°μ΄ν°: \$data'));
Zone β κΈλ‘λ² μλ¬ νΈλ€λ§
runZonedGuardedλ Zone λ΄μ λͺ¨λ λΉλκΈ° μλ¬λ₯Ό μ‘μλ
λλ€. Flutter μ±μ κΈλ‘λ² μλ¬ νΈλ€λ¬λ‘ νμ©λ©λλ€.
import 'dart:async';
runZonedGuarded(
() {
Future.delayed(Duration(seconds: 1), () {
throw Exception('λΉλκΈ° μ€λ₯');
});
},
(error, stack) {
print('Zoneμμ μΊμΉ: \$error');
},
);
Flutter μλ¬ μ²λ¦¬ ν¨ν΄
Flutter μ±μμλ FlutterError.onErrorμ runZonedGuardedλ₯Ό μ‘°ν©νμ¬ λͺ¨λ μμΈλ₯Ό ν¬μ°©ν©λλ€.
void main() {
// Flutter νλ μμν¬ μλ¬
FlutterError.onError = (details) {
if (kReleaseMode) {
Zone.current.handleUncaughtError(
details.exception, details.stack!);
} else {
FlutterError.dumpErrorToConsole(details);
}
};
// κ·Έ μΈ λͺ¨λ λΉλκΈ° μλ¬
runZonedGuarded(
() => runApp(MyApp()),
(error, stackTrace) {
print('μκΈ°μΉ μμ μ€λ₯: \$error');
},
);
}
FutureBuilder / StreamBuilder μλ¬ μ²λ¦¬
μμ ―μμ λΉλκΈ° λ°μ΄ν°λ₯Ό νμν λ snapshot.hasErrorλ‘ μλ¬ μνλ₯Ό 체ν¬ν©λλ€.
FutureBuilder<String>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('μ€λ₯: \${snapshot.error}');
} else if (snapshot.hasData) {
return Text('λ°μ΄ν°: \${snapshot.data}');
}
return Text('λ°μ΄ν° μμ');
},
)
Best Practices
-
✓
ꡬ체μ μΈ μμΈ νμ μ¬μ© β catch-all λμ on FormatException, on HttpException λ±μΌλ‘ λͺ ννκ² λΆκΈ°
-
✓
finallyλ‘ λ¦¬μμ€ μ 리 β νμΌ, DB μ°κ²° λ±μ finallyμμ λ°λμ ν΄μ
-
✓
μμΈ λνμΌλ‘ 컨ν μ€νΈ μ λ¬ β μμλ‘ μ νν λ μμΈ(cause)μ ν¬ν¨ν 컀μ€ν μμΈλ‘ κ°μΈκΈ°
-
✓
μμΈλ μμΈμ μν©μλ§ β νλ¦ μ μ΄ μ©λλ‘ μ¬μ©νμ§ λ§κ³ -1, null λ± λ°νκ° νμ©
-
✓
μ€μνλ μλ¬ νΈλ€λ¬ β ErrorHandler.guard() ν¨ν΄μΌλ‘ λ‘κΉ κ³Ό μλ¬ μ²λ¦¬λ₯Ό ν΅μΌ
μ€μνλ μλ¬ νΈλ€λ¬ ν¨ν΄
class ErrorHandler {
static void logError(Object error, StackTrace stackTrace) {
print('ERROR: \$error');
print('STACK: \$stackTrace');
}
static Future<T> guard<T>(Future<T> Function() fn) async {
try {
return await fn();
} catch (error, stackTrace) {
logError(error, stackTrace);
rethrow;
}
}
}
// μ¬μ©
await ErrorHandler.guard(() async {
final data = await fetchData();
return data;
});
Implementation Steps
try-catch-finally β execute in try, handle in catch, cleanup in finally (close files, disconnect)
on-type handling β branch by exception type with on FormatException catch (e)
rethrow β log in catch then rethrow to preserve original stack trace (unlike throw e)
Async exceptions β use try-catch with async/await, .catchError() for Future chains
Pros
- ✓ Fine-grained handling by exception type with on clause
- ✓ rethrow preserves original stack trace while propagating
Cons
- ✗ Excessive try-catch reduces code readability