Mert Özen Her Satırda Daha İleri
Frontend RxJS — #02

RxJS Creation Function'ları ve Cold vs Hot Observable: of, from, defer, timer, fromEvent ve iif

Mert Özen 14 Kas 2026 15 dk 12 görüntülenme
RxJS Creation Function'ları ve Cold vs Hot Observable: of, from, defer, timer, fromEvent ve iif

Cold Observable her subscriber için producer'ını yeniden başlatır, hot Observable ise subscriber'lardan bağımsız akan bir kaynağa bağlar; Promise döndüren işi Observable'a sararken from değil defer kullanmak gerekir. of, from, range, defer, iif, EMPTY, NEVER, throwError, timer, interval, fromEvent ve fromFetch'i gerçek sunucu loguyla karşılaştırıyor, from(promise) ile retry'ın neden yeni istek atmadığını gösteriyoruz.

Sürüm notu: Bu yazıdaki örnekler RxJS 7.8.2, TypeScript 6.0, Node.js 24 ve Vite 8.3 ile denenmiştir; tarayıcı örneği Playwright 1.65'in Chromium build'inde çalıştırılmıştır.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) fiyat servisi bazen 503 dönüyor. İlk yazıdaki sahte API'yi kullanıyoruz; ?fail=2 parametresi, o kitap için ilk iki isteğe 503 döndürmesini sağlıyor. Üç Observable kuruyoruz ve ikisine retry(2) ile subscribe oluyoruz:

import { defer, from, retry } from "rxjs";

const apiUrl = "http://localhost:5180/api";

interface PriceInfo {
  isbn: string;
  price: number;
  currency: string;
}

// fail = N: sahte API o kitap için ilk N isteğe 503 döner
async function fetchPrice(isbn: string, fail = 0): Promise<PriceInfo> {
  const response = await fetch(`${apiUrl}/books/${isbn}/price?fail=${fail}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return (await response.json()) as PriceInfo;
}

const emmaPrice$ = from(fetchPrice("9780141439587")); // buna kimse subscribe olmuyor
const dunePrice$ = from(fetchPrice("9780441172719", 2));
const hobbitPrice$ = defer(() => fetchPrice("9780547928227", 2));

dunePrice$.pipe(retry(2)).subscribe({
  next: (p) => console.log("Dune:", p.price, p.currency),
  error: (err: Error) => console.log("Dune failed:", err.message),
});
hobbitPrice$.pipe(retry(2)).subscribe({
  next: (p) => console.log("The Hobbit:", p.price, p.currency),
  error: (err: Error) => console.log("The Hobbit failed:", err.message),
});

Sunucu logunda toplam kaç istek görürsün? Emma için istek gider mi? Dune ve The Hobbit'in ikisi de iki 503'ten sonra fiyatı alabilir mi? retry(2)'nin "hata gelirse kaynağa yeniden subscribe ol, en fazla iki kez" demek olduğunu bilmen yeterli; operator'ün ayrıntısı bu serinin error handling yazısında.

İlk yazıda Observable'ı new Observable ile elle kurduk ve her subscribe'ın producer'ı baştan çalıştırdığını gördük. Bu yazıda RxJS'in hazır creation function'larına bakıyoruz: of, from, range, defer, iif, EMPTY, NEVER, throwError, timer, interval, fromEvent ve fromFetch. Hangisinin ne zaman çalıştığını bilmek, "cold" ve "hot" Observable ayrımının da temeli.

Değerlerden Observable: of, from ve range

Elindeki değer zaten hazırsa of ya da from kullanırsın. Aradaki fark küçük görünüyor ama sık hata yapılıyor:

import { from, of, range } from "rxjs";
import { books } from "../../data/books.ts";

const log = (label: string) => (value: unknown) => console.log(label, value);

of("Dune", "Emma").subscribe(log("of:"));
of(["Dune", "Emma"]).subscribe(log("of(array):")); // tek değer: array'in kendisi
from(["Dune", "Emma"]).subscribe(log("from(array):"));

// her iterable olur: Map, Set, string, generator
const stockByTitle = new Map(books.slice(0, 2).map((b) => [b.title, b.stock] as const));
from(stockByTitle).subscribe(log("from(Map):"));

function* catalogPages() {
  yield "page 1";
  yield "page 2";
}
from(catalogPages()).subscribe(log("from(generator):"));

range(1, 3).subscribe(log("range:"));

// Promise ve async iterable: değer hazır olsa bile asenkron
from(Promise.resolve(770)).subscribe(log("from(Promise):"));

async function* orderFeed() {
  yield 1041;
  yield 1042;
}
from(orderFeed()).subscribe(log("from(async iterable):"));

console.log("--- end of synchronous code ---");
of: Dune
of: Emma
of(array): [ 'Dune', 'Emma' ]
from(array): Dune
from(array): Emma
from(Map): [ 'Dune', 12 ]
from(Map): [ 'Emma', 8 ]
from(generator): page 1
from(generator): page 2
range: 1
range: 2
range: 3
--- end of synchronous code ---
from(Promise): 770
from(async iterable): 1041
from(async iterable): 1042

data/books.ts, JavaScript ve TypeScript serilerinde kullandığımız ortak örnek veri: altı kitap (Dune 450 TL, Emma 320 TL…), dört müşteri (Ada, Alan, Grace, Linus) ve sipariş durumları. of argümanlarını olduğu gibi yayar: ona bir array verirsen tek bir değer, yani array'in kendisi gelir. from ise argümanını açar: array, Map, Set, string, generator ve her türlü iterable'ın element'lerini tek tek yayar. range(start, count) ardışık sayılar için kısa yol.

Çıktının sırası da önemli. Array, iterable ve range tamamen senkron: değerler "--- end of synchronous code ---" satırından önce geldi. Promise ve async iterable ise değer hazır olsa bile asenkron teslim edildi, çünkü from onları then ve for await mantığıyla okuyor. from bunlara ek olarak başka bir Observable'ı ve ReadableStream'i de kabul ediyor; RxJS bu girdilerin hepsine ObservableInput diyor ve higher-order operator'lerin (switchMap gibi) kabul ettiği tip de bu.

defer ve iif: Kararı subscribe Anına Bırakmak

Tahminin cevabına geçelim. Sunucuyu node api/server.ts ile başlatıp node src/02/01-predict.ts'i çalıştırınca istemci iki satır yazıyor:

Dune failed: HTTP 503
The Hobbit: 390 TRY

Sunucu logu nedenini gösteriyor:

[api] listening on http://localhost:5180
[api] #1 GET /api/books/9780141439587/price?fail=0
[api] #2 GET /api/books/9780441172719/price?fail=2
[api] #3 GET /api/books/9780547928227/price?fail=2
[api] #1 200
[api] #2 503
[api] #3 503
[api] #4 GET /api/books/9780547928227/price?fail=2
[api] #4 503
[api] #5 GET /api/books/9780547928227/price?fail=2
[api] #5 200

Üç şey oldu. Birincisi, Emma'nın fiyatı (#1) istendi, oysa emmaPrice$'a kimse subscribe olmadı. Sebep from değil: fetchPrice(...) çağrısı from'a argüman olarak verilmeden önce çalışıyor ve Promise oluştuğu anda iş başlıyor. from(promise) lazy değildir, çünkü elindeki Promise zaten çalışmaktadır. İkincisi, Dune için tek istek gitti (#2). retry(2) iki kez yeniden subscribe oldu, ama her seferinde aynı, çoktan reject olmuş Promise'i aldı; sunucuya yeni istek gitmedi ve hata kullanıcıya ulaştı. Üçüncüsü, The Hobbit için üç istek gitti (#3, #4, #5). defer'e Promise değil, Promise üreten bir fonksiyon verdik; defer bu fonksiyonu her subscribe'da yeniden çağırıyor, dolayısıyla her retry yeni bir HTTP isteği demek.

Kural basit: Observable'a Promise döndüren bir fonksiyonu sarman gerekiyorsa from(fn()) değil defer(() => fn()) yaz. defer, ilk yazıdaki "her subscribe producer'ı yeniden çalıştırır" davranışını Promise dünyasına taşıyor. Aynı fikir, değeri subscribe anında hesaplanması gereken her şey için geçerli. iif de bunun özel bir hâli: iki Observable arasındaki seçimi subscribe anına bırakıyor.

import { defer, iif, of } from "rxjs";

const session = { customer: "Grace", isInstitutional: false };

const retailPrice$ = of(450);
const institutionalPrice$ = of(405); // Dune'da %10 kurumsal indirim

// bir kez, bu satır çalışırken karar verilir
const ternaryPrice$ = session.isInstitutional ? institutionalPrice$ : retailPrice$;
// her subscribe'da yeniden karar verilir
const iifPrice$ = iif(() => session.isInstitutional, institutionalPrice$, retailPrice$);

session.isInstitutional = true; // Grace okulunun hesabına geçiyor

ternaryPrice$.subscribe((p) => console.log("ternary:", p));
iifPrice$.subscribe((p) => console.log("iif:", p));

// defer factory'sini her subscribe'da çalıştırır
let cartVersion = 0;
const cartSnapshot$ = defer(() => of(`cart v${++cartVersion}`));
cartSnapshot$.subscribe((v) => console.log("first subscriber:", v));
cartSnapshot$.subscribe((v) => console.log("second subscriber:", v));
ternary: 450
iif: 405
first subscriber: cart v1
second subscriber: cart v2

Ternary operator kararı Observable oluşturulurken verdi; Grace kurumsal hesaba geçtikten sonra bile perakende fiyatı gördü. iif koşul fonksiyonunu subscribe anında çağırdı ve doğru fiyatı verdi. Bookstore'un kurumsal sipariş akışında %10 indirim, kullanıcının o anki hesabına bağlı; bu tür kararları Observable'ı kurarken değil, subscribe anında vermek gerekiyor. iif'in yaptığı iş defer(() => cond() ? a$ : b$) ile aynı; ikiden fazla seçenek varsa doğrudan defer daha okunur.

EMPTY, NEVER ve throwError

Üç özel Observable, operator zincirlerinde "hiçbir şey", "sonsuza kadar bekle" ve "hata" anlamına geliyor:

import { EMPTY, NEVER, throwError, type Observable } from "rxjs";

function watch(label: string, source$: Observable<unknown>) {
  source$.subscribe({
    next: (v) => console.log(label, "next", v),
    error: (err: Error) => console.log(label, "error:", err.message),
    complete: () => console.log(label, "complete"),
  });
}

watch("EMPTY:", EMPTY);
watch("NEVER:", NEVER); // hiçbir zaman bir şey yazmaz
watch("throwError:", throwError(() => new Error("Order 1042 not found")));
console.log("done");
EMPTY: complete
throwError: error: Order 1042 not found
done

EMPTY hiç değer yaymadan hemen tamamlanıyor; bir switchMap içinde "bu durumda istek atma" demenin en kısa yolu. NEVER ne değer yayıyor ne de bitiyor; test yazarken ya da "kullanıcı bir şey yapana kadar bekle" senaryosunda işe yarıyor, ama tamamlanmayı bekleyen bir yere (ör. forkJoin) verilirse orayı da sonsuza kadar bekletiyor. throwError subscribe olunur olmaz error gönderiyor.

throwError'a dikkat: RxJS 7'de doğru kullanım hata nesnesini üreten bir fonksiyon vermek, throwError(() => new Error(...)). Eski throwError(err) imzası hâlâ derleniyor ama RxJS'in type tanımında @deprecated olarak işaretli ve bir sonraki major sürümde kaldırılacağı yazıyor; editör de bu çağrının üstünü çizer. Factory'li imzanın iki faydası var: hata, gerçekten oluştuğu anda yaratıldığı için stack trace doğru yeri gösteriyor ve her subscriber kendi hata nesnesini alıyor.

Zamanla Çalışanlar: timer ve interval

Zamanla değer üreten iki creation function var. Zaman operator'lerini (debounceTime, throttleTime…) ve scheduler'ları bu serinin zaman yazısında ayrıntılı göreceğiz; burada yalnız ilk değerin ne zaman geldiğine bakalım:

import { interval, take, timer } from "rxjs";

const start = performance.now();
const at = () => `${Math.round((performance.now() - start) / 100) * 100} ms`;

// timer(due): gecikmeden sonra tek değer (0), ardından complete
timer(300).subscribe({
  next: (v) => console.log(at(), "timer(300):", v),
  complete: () => console.log(at(), "timer(300): complete"),
});

// timer(0, period): ilk değer hemen, sonra her period'da bir
timer(0, 200).pipe(take(3)).subscribe((v) => console.log(at(), "timer(0, 200):", v));

// interval(period): ilk değer ancak bir period sonra
interval(200).pipe(take(3)).subscribe((v) => console.log(at(), "interval(200):", v));
0 ms timer(0, 200): 0
200 ms interval(200): 0
200 ms timer(0, 200): 1
300 ms timer(300): 0
300 ms timer(300): complete
400 ms interval(200): 1
400 ms timer(0, 200): 2
600 ms interval(200): 2

Süreleri 100 ms'ye yuvarlayarak yazdırdım; üç koşuda da çıktı birebir aynıydı. interval(200)'ün ilk değeri 200 ms'de geldi, timer(0, 200)'ünki hemen. Bookstore'da sipariş durumunu poll ederken bu fark önemli: sayfa açılır açılmaz ilk isteği atmak istiyorsan timer(0, 5000) yazarsın, interval(5000) kullanıcıyı ilk cevap için 5 saniye bekletir. timer'a bir Date de verilebiliyor; o zaman değer o tarihte geliyor (ör. kampanyanın başladığı an). İkisi de tamamlanmıyor (tek argümanlı timer hariç), bu yüzden take ya da unsubscribe olmadan sonsuza kadar çalışıyor.

DOM ve HTTP: fromEvent ve fromFetch

Bookstore'un yönetim ekranında yeni bir kitap eklenirken kapak görseli seçiliyor ve hemen önizleniyor. Snapshot'taki web/ klasörü bunu Vite ile çalışan küçük bir sayfa olarak içeriyor (npm run web):

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Bookstore - cover preview</title>
  </head>
  <body>
    <label for="cover">Cover image</label>
    <input id="cover" type="file" accept="image/*">
    <p id="info">No cover selected</p>
    <img id="preview" alt="Cover preview" width="160" height="240">
    <script type="module" src="./cover-preview.ts"></script>
  </body>
</html>
import { filter, fromEvent, map } from "rxjs";

const input = document.querySelector<HTMLInputElement>("#cover")!;
const info = document.querySelector<HTMLParagraphElement>("#info")!;
const preview = document.querySelector<HTMLImageElement>("#preview")!;

type CoverChoice = { ok: true; file: File } | { ok: false; reason: string };

const coverChoice$ = fromEvent(input, "change").pipe(
  map(() => input.files?.[0]),
  filter((file): file is File => file !== undefined),
  map((file): CoverChoice =>
    file.type.startsWith("image/")
      ? { ok: true, file }
      : { ok: false, reason: `${file.name} is not an image` },
  ),
);

let previewUrl: string | undefined;
coverChoice$.subscribe((choice) => {
  if (!choice.ok) {
    info.textContent = choice.reason;
    return;
  }
  if (previewUrl) URL.revokeObjectURL(previewUrl);
  previewUrl = URL.createObjectURL(choice.file);
  preview.src = previewUrl;
  info.textContent = `${choice.file.name} (${choice.file.size} bytes)`;
});

Sayfayı Playwright ile Chromium'da açıp input'a önce 69 byte'lık bir PNG, sonra bir metin dosyası verdim. #info paragrafı sırasıyla "No cover selected", "dune-cover.png (69 bytes)" ve "dune.txt is not an image" oldu; önizleme görselinin src'si bir blob: URL'i aldı. fromEvent, subscribe olunca element'e bir event listener ekliyor, unsubscribe olunca kaldırıyor; teardown'ı RxJS yazıyor. İlk yazıdaki kuralı da uyguladık: dosya kontrolü map içinde, subscribe yalnız ekrana yazıyor. accept="image/*" yalnız dosya seçicisinde bir öneri; kullanıcı yine de başka türde dosya seçebiliyor, kontrolü kodda yapmak bu yüzden gerekli.

HTTP için RxJS'in kendi fromFetch'i var (rxjs/fetch'ten import edilir). İlk yazıda elle yazdığımız getStock'un hazır hâli gibi: lazy, her subscribe'da yeni istek, unsubscribe'da AbortController ile iptal. Ama bir farkı var:

import { throwError } from "rxjs";
import { fromFetch } from "rxjs/fetch";

const apiUrl = "http://localhost:5180/api";

// 1) fromFetch lazy: istek subscribe ile başlar
const dune$ = fromFetch(`${apiUrl}/books/9780441172719`, {
  selector: (response) => response.json(),
});
dune$.subscribe((book) => console.log("1) book:", book.title, book.price));

// 2) fetch için 404 hata değildir: response.json() boş gövdede patlar
const missing$ = fromFetch(`${apiUrl}/books/9780000000000`, {
  selector: (response) => response.json(),
});
missing$.subscribe({
  next: (book) => console.log("2) book:", book),
  error: (err: Error) => console.log("2) error:", err.name, "-", err.message),
});

// 3) response.ok'u kendin kontrol et, HTTP hatasını error bildirimine çevir
const checked$ = fromFetch(`${apiUrl}/books/9780000000000`, {
  selector: (response) =>
    response.ok ? response.json() : throwError(() => new Error(`HTTP ${response.status}`)),
});
checked$.subscribe({ error: (err: Error) => console.log("3) error:", err.message) });

// 4) cevaptan önce unsubscribe: istek iptal edilir
const sub = fromFetch(`${apiUrl}/books`).subscribe(() => console.log("4) never printed"));
setTimeout(() => sub.unsubscribe(), 50);
1) book: Dune 450
2) error: SyntaxError - Unexpected end of JSON input
3) error: HTTP 404
[api] #1 GET /api/books/9780441172719
[api] #2 GET /api/books/9780000000000
[api] #3 GET /api/books/9780000000000
[api] #4 GET /api/books
[api] #4 client aborted
[api] #1 200
[api] #2 404
[api] #3 404

Fetch, 404 ve 500 gibi cevapları hata saymıyor; yalnız ağ hatasında reject ediyor. fromFetch da bu kurala uyuyor. İkinci örnekte asıl sorun (kitap yok) bir JSON parse hatasının arkasına saklandı; log'a bakan biri "API bozuk JSON döndürüyor" diye yanlış yere bakar. Üçüncü örnekteki gibi selector içinde response.ok'u kontrol etmek gerekiyor. selector vermezsen fromFetch Response nesnesini yayıp hemen tamamlanıyor; gövdeyi sonradan okursan o okuma artık iptal edilemiyor. Bu yüzden gövdeyi her zaman selector içinde oku. Angular projesinde HTTP için zaten HttpClient kullanıyoruz; fromFetch, Angular dışındaki kodda ya da küçük script'lerde işe yarıyor.

Cold ve Hot Observable

Buraya kadar gördüklerimizi iki gruba ayırabiliriz. Cold Observable'da producer subscribe ile başlar ve her subscriber'ın kendi producer'ı olur. Hot Observable'da producer subscriber'lardan bağımsız çalışır; subscriber'lar zaten akan bir kaynağa bağlanır. Önce cold:

import { interval, take } from "rxjs";

// cold: her subscriber kendi interval'ını alır, 0'dan başlar
const cold$ = interval(100).pipe(take(4));

cold$.subscribe((v) => console.log("cold A:", v));
setTimeout(() => cold$.subscribe((v) => console.log("cold B:", v)), 250);
cold A: 0
cold A: 1
cold A: 2
cold B: 0
cold A: 3
cold B: 1
cold B: 2
cold B: 3

B, 250 ms geç geldi ama hiçbir şey kaçırmadı: kendi sayacı 0'dan başladı. Şimdi hot bir kaynak. Depo, stok yenilendiğinde event yayıyor; dinleyen olsun olmasın event'ler gerçekleşiyor. Node.js'te de EventTarget ve CustomEvent olduğu için örneği tarayıcısız çalıştırabiliyoruz:

import { fromEvent, map } from "rxjs";

// hot: depo, dinleyen olsun olmasın stok event'i yayar
const warehouse = new EventTarget();
const restocked$ = fromEvent(warehouse, "restocked").pipe(
  map((event) => (event as CustomEvent<string>).detail),
);

["Dune", "Emma", "The Hobbit", "Middlemarch"].forEach((title, i) => {
  setTimeout(() => {
    console.log(`warehouse: ${title} restocked`);
    warehouse.dispatchEvent(new CustomEvent("restocked", { detail: title }));
  }, 100 * (i + 1));
});

restocked$.subscribe((title) => console.log("  hot A:", title));
setTimeout(() => restocked$.subscribe((title) => console.log("  hot B:", title)), 250);
warehouse: Dune restocked
  hot A: Dune
warehouse: Emma restocked
  hot A: Emma
warehouse: The Hobbit restocked
  hot A: The Hobbit
  hot B: The Hobbit
warehouse: Middlemarch restocked
  hot A: Middlemarch
  hot B: Middlemarch

B, Dune ve Emma'yı kaçırdı; o event'ler B subscribe olmadan önce gerçekleşmişti ve tekrar gelmeyecek. Teknik olarak fromEvent'in kendisi de her subscribe'da yeni bir listener ekliyor, yani mekanizması cold Observable gibi; ama sarmaladığı kaynak (DOM event'leri, depo) hot. Pratikte sorulacak soru şu: "Geç gelen subscriber, kaçırdığı değerleri alabilir mi, ve iki subscriber aynı işi iki kez yaptırır mı?"

KaynakTürİkinci subscriber
of, from(array), rangeColdAynı değerleri baştan alır
interval, timerColdKendi sayacını 0'dan başlatır
defer, fromFetch, new ObservableColdProducer yeniden çalışır (yeni istek)
from(promise)Promise zaten çalışıyorAynı tek sonucu alır, yeni istek yok
fromEventHot kaynakYalnız subscribe olduktan sonraki event'leri alır

Cold bir Observable'ı hot'a çevirmek, yani tek bir producer'ı birden çok subscriber arasında paylaşmak bu serinin multicasting yazısının konusu; Subject'ler de hot kaynakların RxJS'teki karşılığı ve Subject yazısında anlatılıyor.

Üretimde Dikkat

Creation function seçimi doğrudan ağ trafiğine yansıyor. Bookstore'un ürün sayfasında fiyat, stok ve yorumlar için Promise döndüren eski bir service'i Observable'a sarıyorsan defer kullan; from(service.getPrice()) sayfa daha render edilmeden, belki hiç kullanılmayacak bir isteği başlatır ve retry'ı işe yaramaz hâle getirir. Bunu Tahmin örneğinde sunucu logunda gördük: Emma'nın isteği kimse istemeden gitti, Dune'un retry'ı sunucuya hiç ulaşmadı.

Ölçerken DevTools'un Network sekmesinde iki şeye bak: bir isteğin "Initiator" sütunu (isteği hangi kod satırı başlattı) ve retry sırasında aynı URL'e gerçekten yeni istek gidip gitmediği. Retry'ın sunucuya ulaşmadığı bir hata, log'da yalnız "tek bir 503" olarak görünür ve kolay gözden kaçar. fromEvent tarafında ise listener sayısı ölçülecek şey: Chromium DevTools konsolundaki getEventListeners(element) yardımcı fonksiyonu, unsubscribe edilmeyen subscription'ların bıraktığı listener'ları gösterir. Angular'da component yok olurken unsubscribe etmeyi unutmak, her sayfa ziyaretinde bir listener daha bırakır.

Zamanlı kaynaklarda (interval, timer(0, n)) da aynı soru var: kim, ne zaman unsubscribe ediyor? Tamamlanmayan her creation function için bir bitiş noktası (take, takeUntil, component'in yok olması) tanımlanmadıysa, bu bir sızıntıdır.

Sık Düşülen Tuzaklar

  • of(fetch(url)) ya da from(fetch(url)) yazmak. İstek Observable oluşturulurken başlar, subscribe beklenmez; retry ve yeniden subscribe yeni istek atmaz. Doğrusu defer(() => fetch(url)) ya da fromFetch(url).
  • of(array) ile from(array)'i karıştırmak. of array'i tek değer olarak yayar; her element'i ayrı değer olarak istiyorsan from.
  • Deprecated throwError(err) imzasını kullanmak. throwError(() => new Error(...)) yaz; hata gerçekten oluştuğu anda yaratılır.
  • fromFetch'in 404 ve 500'de hata vereceğini sanmak. Fetch yalnız ağ hatasında reject eder; response.ok'u selector içinde kontrol et.
  • Ternary operator ile Observable seçip kararın subscribe anında verileceğini sanmak. O karar satır çalıştığında bir kez verilir; değişebilecek durumlar için iif ya da defer.
  • interval ile poll ederken ilk değerin hemen geleceğini varsaymak. İlk değer bir period sonra gelir; hemen başlamak için timer(0, period).

Küçük Bir Deneme

★☆☆ 01-predict.ts'teki dunePrice$'ı defer ile yeniden yaz ve fail=2 ile çalıştır. Sunucu logunda Dune için kaç istek görüyorsun? Sonra fail=3 yap; retry(2) ile sonucun neden değiştiğini açıkla.

★★☆ 08-hot.ts'teki depo örneğinde B'nin kaçırdığı event'leri de görebilmesi için ne değişmeli? Önce restocked$'ı kendi new Observable'ınla sarmalayıp son event'i saklamayı dene; sonra bu yaklaşımın hangi durumda yanlış sonuç vereceğini (ör. depo bir kitabı tekrar tükettiğinde) not et. Hazır çözümü Subject ve multicasting yazılarında göreceğiz.

★★★ Kapak önizleme sayfasına, seçilen dosya 2 MB'tan büyükse önizleme yerine uyarı gösteren bir kural ekle. Kuralı map içinde CoverChoice tipine yeni bir durum olarak ekle ve subscribe tarafını yalnız ekrana yazan kod olarak bırak. Playwright ya da tarayıcı ile 3 MB'lık sahte bir dosya seçerek dene.

Artık Observable'ları üretebiliyoruz; sıradaki soru onları nasıl dönüştüreceğimiz. Bir sonraki yazıda marble diagram okumayı öğrenip map, filter, scan, take ve distinctUntilChanged gibi temel operator'lere bakacağız: Bookstore sepetinde scan ile ara toplamı tutacak, aynı filtre değeri tekrar geldiğinde gereksiz isteği atlayacağız.