Xato va xotira
Production Angular ilovasida xatolarni to'g'ri ushlash, retry strategiyasi va subscription'larni tozalash — barqarorlik va xotira sızıntısiz ishlash uchun majburiy.
catchError — xatoni ushlash
Nima bu?
catchError — stream ichidagi xatoni ushlab, fallback Observable qaytaradi yoki xatoni transform qiladi. HTTP 404/500, network xato va operator zanjirida stream'ni to'liq to'xtatmasdan davom ettirish uchun. throwError(() => err) bilan qayta throw yoki of(defaultValue) bilan default qiymat.
Kod misoli
import { catchError, of, throwError } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class UserService {
getUser(id: number) {
return this.http.get<User>(`/api/users/${id}`).pipe(
catchError((err: HttpErrorResponse) => {
if (err.status === 404) {
return of(null as User | null); // fallback
}
console.error('User fetch failed:', err.message);
return throwError(() => new Error('Foydalanuvchi yuklanmadi'));
})
);
}
}
// Global interceptor bilan birga
this.http.get('/api/data').pipe(
catchError((err) => {
this.errorService.report(err);
return of([]); // UI bo'sh ro'yxat ko'rsatadi
})
);
Imtihonda
- catchError va subscribe ichidagi error callback farqi?
- catchError ichida qayta throw qilish kerak bo'lsa nima qilinadi?
Yodlash uchun
catchError = zanjirda xato ushlash + fallback Observable qaytarish.
retry, retryWhen — qayta urinish
Nima bu?
retry(n) — xato bo'lganda avtomatik n marta qayta subscribe. retry({ count, delay }) — kechikish bilan. retryWhen — custom qayta urinish logikasi (exponential backoff). Vaqtinchalik network xatolari uchun; 401/404 kabi doimiy xatolarda retry foydasiz.
Gotcha: RxJS 7'dan boshlab retryWhen deprecated — endi retry({ delay: (error, retryCount) => timer(retryCount * 1000) }) orqali xuddi shu exponential backoff logikasi retry() ichida yoziladi, alohida retryWhen operatoriga ehtiyoj qolmagan. Yangi kodda retryWhen ko'rsangiz — bu legacy signal, refactor qilish kerak.
Kod misoli
import { retry, retryWhen, delayWhen, scan, take, tap } from 'rxjs/operators';
import { timer } from 'rxjs';
// Oddiy retry — 3 marta
this.http.get('/api/unstable').pipe(
retry(3),
catchError((err) => of(null))
);
// Delay bilan (RxJS 7+)
this.http.get('/api/data').pipe(
retry({ count: 3, delay: 1000 })
);
// Exponential backoff — retryWhen (1s, 2s, 3s... kutish)
this.http.get('/api/data').pipe(
retryWhen((errors) =>
errors.pipe(
tap((err) => console.log('Retry...', err.status)),
scan((attempt) => attempt + 1, 0),
delayWhen((attempt) => timer(attempt * 1000)),
take(3)
)
)
);
Imtihonda
- Qaysi HTTP status kodlarida retry mantiqiy?
- retry va catchError tartibi muhimmi?
Yodlash uchun
Vaqtinchalik xato (503, network) → retry. Doimiy (401, 404) → catchError.
takeUntil — xotira sızdırmasi oldini olish
Nima bu?
takeUntil(notifier$) — notifier emit qilguncha stream'dan qiymat oladi, keyin avtomatik unsubscribe. Component destroy, modal yopilishi yoki bir nechta subscription'ni bitta destroy$ bilan tozalash uchun standart pattern.
Gotcha: takeUntil pipe zanjirida oxirgi operator bo'lishi kerak. Agar u switchMap/mergeMapdan oldin turib qolsa, tashqi stream to'xtaydi, lekin ichki (masalan, HTTP) Observable hali ham davom etadi va natija kelganda componentga urinib ko'radi — bu klassik "operator tartibi" xatosi va aynan shu sababli senior darajada tez-tez so'raladi.
Kod misoli
import { Component, OnDestroy } from '@angular/core';
import { Subject, interval } from 'rxjs';
import { takeUntil, switchMap } from 'rxjs/operators';
@Component({ /* ... */ })
export class LiveDataComponent implements OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit() {
interval(1000)
.pipe(takeUntil(this.destroy$))
.subscribe((n) => (this.tick = n));
this.route.paramMap
.pipe(
takeUntil(this.destroy$),
switchMap((p) => this.loadData(p.get('id')!))
)
.subscribe((data) => (this.data = data));
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
Imtihonda
- takeUntil va qo'lda unsubscribe afzalliklari?
- destroy$ ni complete qilish kerakmi?
Yodlash uchun
destroy$ + takeUntil = barcha stream'lar bir joyda to'lanadi.
takeUntilDestroyed() — Angular 16+ avtomat
Nima bu?
takeUntilDestroyed() — Angular 16+ da DestroyRef bilan component/directive destroy bo'lganda avtomatik unsubscribe. ngOnDestroy va destroy$ Subject yozish shart emas. Constructor yoki injection context ichida chaqirilishi kerak.
Kod misoli
import { Component, DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({ /* ... */ })
export class ModernComponent {
private destroyRef = inject(DestroyRef);
private route = inject(ActivatedRoute);
// Constructor injection context
constructor() {
interval(1000)
.pipe(takeUntilDestroyed())
.subscribe((n) => (this.count = n));
}
// Yoki destroyRef explicit
ngOnInit() {
this.route.paramMap
.pipe(
switchMap((p) => this.api.get(p.get('id')!)),
takeUntilDestroyed(this.destroyRef)
)
.subscribe((data) => (this.data = data));
}
}
Imtihonda
- takeUntilDestroyed qayerda chaqiriladi?
- Service ichida ishlatish mumkinmi?
Yodlash uchun
takeUntilDestroyed = destroy$ + ngOnDestroy o'rniga. Constructor/injection context.
async pipe — avtomat subscribe/unsubscribe
Nima bu?
async pipe — template'da Observable yoki Promise'ni subscribe qiladi, qiymatni ko'rsatadi va component destroy'da avtomatik unsubscribe. Change detection bilan integratsiya. Manual subscribe, ngOnDestroy va memory leak xavfini kamaytiradi — zamonaviy Angular'da tavsiya etiladi.
Kod misoli
@Component({
template: `
@if (user$ | async; as user) {
<h1>{{ user.name }}</h1>
<p>{{ user.email }}</p>
} @else {
<p>Yuklanmoqda...</p>
}
@for (product of products$ | async; track product.id) {
<app-product-card [product]="product" />
}
`,
})
export class ProfileComponent {
user$ = this.http.get<User>('/api/me');
products$ = this.productService.getAll();
}
Imtihonda
- async pipe memory leak oldini oladimi?
- Bir xil Observable template'da 3 marta | async — muammo bormi?
Yodlash uchun
async pipe = auto subscribe + unsubscribe. Bir nechta joyda — @let yoki as alias.
shareReplay(1) — bitta HTTP, ko'p subscriber
Nima bu?
shareReplay({ bufferSize: 1, refCount: true }) — cold Observable'ni multicast qiladi: birinchi subscriber HTTP ishga tushiradi, natija cache'lanadi, keyingi subscriber'lar cache'dan oladi. refCount: true — barcha subscriber ketganda subscription to'lanadi. Config, permissions, reference data uchun.
Gotcha: agar manba error qaytarsa, shareReplay xatoni ham cache'laydi — keyingi barcha subscriber'lar (hatto network tiklangandan keyin ham) xuddi shu eski xatoni qayta-qayta oladi, chunki manba qayta ishga tushmaydi. Bu — production'da "bir marta xato bo'lsa, butun ilova abadiy shu xatoni ko'rsatadi" degan real bug manbai; yechim — catchError bilan xatoni tutib fallback qaytarish yoki stream'ni qayta yaratish logikasi.
Kod misoli
@Injectable({ providedIn: 'root' })
export class AppConfigService {
private config$ = this.http.get<AppConfig>('/api/config').pipe(
shareReplay({ bufferSize: 1, refCount: true })
);
getConfig() {
return this.config$;
}
}
// 3 ta component subscribe — 1 ta HTTP
@Component({ /* A */ })
class HeaderComponent {
config$ = inject(AppConfigService).getConfig();
}
@Component({ /* B */ })
class FooterComponent {
config$ = inject(AppConfigService).getConfig();
}
Imtihonda
- shareReplay(1) va BehaviorSubject farqi?
- refCount: false bo'lsa nima bo'ladi?
Yodlash uchun
shareReplay(1) = HTTP cache + hot multicast. refCount: true = tozalash.
