Reaktif Programlamada Hata Yakalama ve Yönetimi Nasıl Yapılır?
Merhaba, reaktif programlama meraklıları! Bugün, asenkron veri akışlarının büyülü dünyasında bir yolculuğa çıkacağız. Ama dikkat edin, bu yolculuk her zaman pürüzsüz olmayabilir. Neyse ki, reaktif programlamada hataları yakalamak ve yönetmek için güçlü araçlarımız var. Haydi, bu araçları keşfedelim ve kodumuzun dalgalı denizlerinde nasıl güvenle yüzeceğimizi öğrenelim!
1. Temel Hata Yakalama: catchError Operatörü
Reaktif programlamada en temel hata yakalama mekanizması, catchError operatörüdür. Bu, try-catch bloklarının reaktif karşılığı gibidir.
import { of } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; const source$ = of(1, 2, 3, 4, 5); source$.pipe( map(val => { if (val === 4) throw new Error('Dört sayısı yasak!'); return val; }), catchError(err => of('Hata yakalandı: ' + err.message)) ).subscribe( val => console.log(val), err => console.error(err), () => console.log('Tamamlandı') );
Bu örnekte, 4 sayısına geldiğimizde bir hata fırlatıyoruz. catchError operatörü bu hatayı yakalar ve yerine yeni bir Observable döndürür.
2. Hata Sonrası Kurtarma: retry Operatörü
Bazen, özellikle ağ isteklerinde, bir işlemi yeniden denemek isteriz. İşte burada retry operatörü devreye girer.
import { interval } from 'rxjs'; import { mergeMap, retry } from 'rxjs/operators'; const source$ = interval(1000); source$.pipe( mergeMap(val => { if (val > 3) throw new Error('Çok yüksek!'); return of(val); }), retry(2) ).subscribe( val => console.log(val), err => console.error('Hata:', err.message) );
Bu kod, hata alındığında işlemi 2 kez daha deneyecek. Eğer hala başarısız olursa, en son hatayı fırlatacak.
3. Koşullu Yeniden Deneme: retryWhen Operatörü
Daha karmaşık yeniden deneme mantığı için retryWhen operatörünü kullanabiliriz. Bu, exponential backoff gibi stratejiler uygulamanıza olanak tanır.
import { timer, throwError } from 'rxjs'; import { retryWhen, mergeMap } from 'rxjs/operators'; source$.pipe( retryWhen(errors => errors.pipe( mergeMap((error, index) => { if (index > 3) { return throwError(error); } console.log(`Deneme ${index + 1}: ${error.message}`); return timer(index * 1000); }) ) ) ).subscribe( val => console.log(val), err => console.error('Son hata:', err.message) );
Bu örnekte, her hata sonrası bekleme süresi artırılıyor ve maksimum 3 deneme yapılıyor.
4. Hata Dönüşümü: throwError Operatörü
Bazen bir hatayı yakaladıktan sonra, onu dönüştürüp yeniden fırlatmak isteyebiliriz. throwError operatörü tam da bu iş için.
import { throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; source$.pipe( catchError(err => { if (err.status === 404) { return throwError(new Error('Kaynak bulunamadı')); } return throwError(err); }) ).subscribe( val => console.log(val), err => console.error('Hata:', err.message) );
Bu örnekte, 404 hatasını yakalayıp daha anlamlı bir hata mesajına dönüştürüyoruz.
5. Tamamlanma ve Hata Durumları: finalize Operatörü
finalize operatörü, bir Observable'ın tamamlanması veya hata vermesi durumunda çalışacak kodu belirlememizi sağlar.
import { finalize } from 'rxjs/operators'; source$.pipe( finalize(() => console.log('İşlem tamamlandı veya hata verdi')) ).subscribe( val => console.log(val), err => console.error('Hata:', err.message), () => console.log('Tamamlandı') );
Bu, kaynakları temizlemek veya loading spinnerları kapatmak için idealdir.
6. Çoklu Hata Kaynakları: mergeMap ve catchError Kombinasyonu
Birden fazla asenkron işlem yapıyorsanız, her birinin hatasını ayrı ayrı yönetmek isteyebilirsiniz.
import { of, forkJoin } from 'rxjs'; import { mergeMap, catchError } from 'rxjs/operators'; const api1$ = of('API 1 Verisi').pipe( mergeMap(() => throwError(new Error('API 1 Hatası'))) ); const api2$ = of('API 2 Verisi'); forkJoin({ api1: api1$.pipe(catchError(err => of(`API 1 Hatası: ${err.message}`))), api2: api2$ }).subscribe( result => console.log(result), err => console.error(err), () => console.log('Tüm işlemler tamamlandı') );
Bu yaklaşım, bir API'nin hata vermesi durumunda diğer API çağrılarının etkilenmemesini sağlar.
7. Özel Hata Tipleri: Tiplendirilmiş Hatalar
Typescript kullanıyorsanız, özel hata tipleri oluşturarak hata yönetiminizi daha güçlü hale getirebilirsiniz.
class NetworkError extends Error { constructor(public status: number, message?: string) { super(message); this.name = 'NetworkError'; } } source$.pipe( catchError(err => { if (err instanceof NetworkError && err.status === 500) { return of('Sunucu hatası, lütfen daha sonra tekrar deneyin'); } return throwError(err); }) ).subscribe( val => console.log(val), err => console.error('Hata:', err.message) );
Bu yaklaşım, farklı hata tiplerini daha etkili bir şekilde ayırt etmenizi ve yönetmenizi sağlar.
Hata Yönetimi, Reaktif Programlamanın Kalbidir!
İşte böyle, reaktif programlama kahramanları! Gördüğünüz gibi, hata yakalama ve yönetimi, reaktif programlamanın kritik bir parçası. Bu teknikleri kullanarak, kodunuzu daha dayanıklı, daha güvenilir ve daha kolay yönetilebilir hale getirebilirsiniz.
Unutmayın, mükemmel hata yönetimi stratejisi, uygulamanızın ihtiyaçlarına göre değişecektir. Sürekli test edin, farklı senaryoları düşünün ve kullanıcı deneyimini optimize edin.
Şimdi, bu bilgilerle donanmış olarak, gidin ve reaktif uygulamalarınızı hata toleranslı hale getirin! Kim bilir, belki de bir gün birileri kodunuza bakıp "Vay be, bu uygulama nasıl bu kadar sağlam çalışıyor?" diyecek. Ve o zaman, gururla gülümseyip "Evet, bu bir reaktif programlama sihri!" diyebileceksiniz.
Akışlarınız pürüzsüz, hatalarınız yönetilebilir olsun!