Asinxron kodda xəta emalı
async funksiyada try/catch/finally sinxron kodda olduğu kimi işləyir — bir şərtlə: xəta await edilmiş çağırışdan gəlməlidir.
try { await repo.refresh(); } catch (e) { ... }
Burada await iki iş görür: nəticəni gözləyir və Future-un xətasını cari frame-ə bağlayır — yəni həmin xəta bu try blokunun içindən atılmış kimi davranır.
await yazmasan, funksiya Future-u yaradıb dərhal irəli gedir; try bloku bitir, sonra Future fail olur — və artıq onu tutacaq heç bir catch qalmır. Bu, "floating future" adlanır və Dart interview-larının ən sevimli sualıdır.
finally uğur, xəta və rethrow hallarının hamısında icra olunur — resurs bağlamaq (StreamSubscription.cancel(), client.close()) üçün doğru yer məhz odur.
| Mexanizm | Nəyi tutur | Nə vaxt istifadə edilir |
|---|---|---|
| `try/await/catch` | Sinxron `throw`-ları və `await` edilmiş Future-un xətasını | Default seçim — kodun 90%-i belə yazılır |
| `on FormatException catch (e, st)` | Yalnız həmin tipdəki xətanı, stack trace ilə birlikdə | Fərqli xəta tiplərinə fərqli reaksiya lazım olanda |
| `future.catchError(...)` | Həmin Future zəncirindəki xətanı; `test:` parametri ilə filtr edilə bilər | `await` mümkün olmayan yerlərdə (məs. `unawaited(...)` ilə fire-and-forget) |
| `future.then(onValue, onError: ...)` | Yalnız ƏVVƏLKİ mərhələnin xətasını — `onValue` içində atılan xətanı tutmur | İki halı ayırmaq lazım olanda; incə fərqinə görə interview-da tez-tez soruşulur |
| `stream.listen(onError: ..., cancelOnError: ...)` | Həmin subscription-a çatan xətaları (default olaraq subscription davam edir) | Listener tərəfində — UI-a xəta göstərmək, log yazmaq |
| `stream.handleError(..., test: ...)` | Pipeline-dan keçən xətaları — onları udur və ya çevirir | Stream-i emal edən zəncirin ortasında, listener-i təmiz saxlamaq üçün |
| `runZonedGuarded(body, onError)` | Zone daxilində heç kimin tutmadığı bütün async xətaları | Son müdafiə xətti — crash reporting (Sentry, Crashlytics) |
`Error` və `Exception` fərqi Dart-da konvensiyadır, dil qaydası deyil — hər ikisini throw etmək olar, hər ikisi catch (e) ilə tutulur. Fərq niyyətdədir:
- `Exception` — gözlənilən, bərpa oluna bilən vəziyyət: şəbəkə düşdü, JSON pozuqdur, kart rədd edildi. Sən bunları atırsan və çağıran tərəf tutur.
- `Error` — proqramçı səhvi:
ArgumentError,StateError,RangeError,LateInitializationError,TypeError. Bunlar tutulmaq üçün deyil, düzəldilmək üçündür. Kitabxana yazırsansa, kontraktı pozan çağırışaArgumentErroratmaq düzgündür; onucatchetmək isə demək olar ki, həmişə səhvdir.
Öz exception sinifin: class PaymentDeclined implements Exception — extends yox, implements (Exception sadəcə interfeysdir). Həmişə toString() override et, əks halda log-da faydasız Instance of 'PaymentDeclined' görürsən. Sahələr əlavə et (code, retryable) — çağıran tərəf mətn parse etməməlidir.
`rethrow` və stack trace: catch (e) { throw e; } stack trace-i sıfırlayır və xətanın əsl mənbəyini itirirsən. rethrow orijinal trace-i saxlayır. Xətanı başqa tipə "bükürsənsə", trace-i əl ilə ötür: Error.throwWithStackTrace(CheckoutFailure(e), st).
Interview tövsiyəsi: sənə çox güman ki, await-siz çağırışı olan bir kod parçası göstərib "niyə catch işləmir?" soruşacaqlar. Cavabı mexanizmlə ver, hafizədən deyil: "await Future-un xətasını cari frame-ə bağlayır; await yoxdursa, try bloku Future fail olmamışdan əvvəl bitir, ona görə xəta zone-a qalxır və unhandled olur." Sonra üç həll təklif et: await əlavə et, niyyət fire-and-forget-dirsə unawaited(... .catchError(...)) yaz, və ya Future.wait ilə hamısını topla. Bu bir cavabla həm dil modelini, həm də praktikanı göstərirsən.
📚 Mənbələr və sənədlər
- Xəta emalırəsmidart.dev