Skip to content

generate() - Universelle Schleifengenerierung

generate() ist eine Creation Function zur Implementierung flexibler Schleifenverarbeitung als Observable durch Angabe von Anfangszustand, Fortsetzungsbedingung, Zustandsaktualisierung und Ergebnisauswahl.

Übersicht

generate() ermöglicht die deklarative Beschreibung flexibler Schleifenverarbeitungen wie while- oder for-Schleifen. Wird verwendet, wenn komplexere Bedingungen oder Zustandsverwaltung als bei range() erforderlich sind.

Signatur:

typescript
function generate<T, S>(
  initialState: S,
  condition: (state: S) => boolean,
  iterate: (state: S) => S,
  resultSelector?: (state: S) => T,
  scheduler?: SchedulerLike
): Observable<T>

Parameter:

  • initialState: Anfangszustand der Schleife
  • condition: Funktion zur Prüfung der Fortsetzungsbedingung (Schleife endet bei false)
  • iterate: Funktion zum Übergang zum nächsten Zustand (Zustandsaktualisierung)
  • resultSelector: Funktion zur Auswahl des zu emittierenden Werts aus dem Zustand (bei Auslassung wird Zustand selbst emittiert)
  • scheduler: Scheduler für die Wertemission (bei Auslassung synchron)

Offizielle Dokumentation: 📘 RxJS Offiziell: generate()

Grundlegende Verwendung

Muster 1: Einfacher Zähler

Grundlegendste Verwendung.

typescript
import { generate } from 'rxjs';

// Zählt von 1 bis 5
generate(
  1,              // Anfangszustand
  x => x <= 5,    // Fortsetzungsbedingung
  x => x + 1      // Zustandsaktualisierung
).subscribe({
  next: value => console.log('Wert:', value),
  complete: () => console.log('Abgeschlossen')
});

// Ausgabe:
// Wert: 1
// Wert: 2
// Wert: 3
// Wert: 4
// Wert: 5
// Abgeschlossen

Dieser Code ist äquivalent zu folgender while-Schleife:

typescript
let x = 1;
while (x <= 5) {
  console.log('Wert:', x);
  x = x + 1;
}
console.log('Abgeschlossen');

Muster 2: Werttransformation mit resultSelector

Zustand und emittierter Wert können getrennt werden.

typescript
import { generate } from 'rxjs';

// Interner Zustand ist Zähler, aber emittiert wird der quadrierte Wert
generate(
  1,              // Anfangszustand: 1
  x => x <= 5,    // Fortsetzungsbedingung: x <= 5
  x => x + 1,     // Zustandsaktualisierung: x + 1
  x => x * x      // Ergebnisauswahl: emittiert x^2
).subscribe(console.log);

// Ausgabe: 1, 4, 9, 16, 25

Muster 3: Komplexes Zustandsobjekt

Als Zustand kann ein komplexes Objekt verwendet werden.

typescript
import { generate } from 'rxjs';

interface State {
  count: number;
  sum: number;
}

// Berechnet kumulative Summe
generate<number, State>(
  { count: 1, sum: 0 },           // Anfangszustand
  state => state.count <= 5,      // Fortsetzungsbedingung
  state => ({                     // Zustandsaktualisierung
    count: state.count + 1,
    sum: state.sum + state.count
  }),
  state => state.sum              // Ergebnisauswahl
).subscribe(console.log);

// Ausgabe: 0, 1, 3, 6, 10
// (0, 0+1, 0+1+2, 0+1+2+3, 0+1+2+3+4)

Wichtige Eigenschaften

1. While-Schleifen-ähnliches Verhalten

generate() ermöglicht flexible Kontrolle wie eine while-Schleife.

typescript
import { generate } from "rxjs";

// while-Schleife
let i = 1;
while (i <= 10) {
  console.log(i);
  i = i * 2;
}

// Gleiche Funktionalität mit generate()
generate(
  1,              // let i = 1;
  i => i <= 10,   // while (i <= 10)
  i => i * 2      // i = i * 2;
).subscribe(console.log);

// Ausgabe: 1, 2, 4, 8

2. Synchrone Emission

Standardmäßig werden alle Werte synchron unmittelbar nach Subscription emittiert.

typescript
import { generate } from 'rxjs';

console.log('Vor Subscription');

generate(1, x => x <= 3, x => x + 1).subscribe(val => console.log('Wert:', val));

console.log('Nach Subscription');

// Ausgabe:
// Vor Subscription
// Wert: 1
// Wert: 2
// Wert: 3
// Nach Subscription

3. Vorsicht vor Endlosschleifen

Wenn die Bedingung immer true ist, entsteht eine Endlosschleife.

typescript
import { generate, take } from 'rxjs';
// ❌ Gefährlich: Endlosschleife (Browser friert ein)
// generate(0, x => true, x => x + 1).subscribe(console.log);

// ✅ Sicher: Anzahl mit take() begrenzen
generate(
  0,
  x => true,  // Immer true
  x => x + 1
).pipe(
  take(10)    // Nur erste 10 holen
).subscribe(console.log);

// Ausgabe: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

WARNING

Vorsicht vor Endlosschleifen:

  • Bei Bedingungen, die immer true werden, entsteht Endlosschleife
  • Emissionszahl mit take(), takeWhile(), takeUntil() begrenzen
  • Oder angemessene Abbruchbedingung in Bedingungsfunktion setzen

Praktische Anwendungsfälle

1. Fibonacci-Folge

Beispiel für komplexe Zustandsübergänge.

typescript
import { generate, take } from 'rxjs';
interface FibState {
  current: number;
  next: number;
}

// Erste 10 Glieder der Fibonacci-Folge
generate<number, FibState>(
  { current: 0, next: 1 },           // Anfangszustand: F(0)=0, F(1)=1
  state => true,                     // Unendlich generieren
  state => ({                        // Zustandsaktualisierung
    current: state.next,
    next: state.current + state.next
  }),
  state => state.current             // Aktuellen Wert emittieren
).pipe(
  take(10)                           // Erste 10 Glieder
).subscribe(console.log);

// Ausgabe: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

2. Exponentieller Backoff

Generierung exponentieller Wartezeiten für Retry-Verarbeitung.

typescript
import { generate } from 'rxjs';

interface RetryState {
  attempt: number;
  delay: number;
}

// Generiert Backoff-Verzögerungszeiten (1s, 2s, 4s, 8s, 16s)
generate<number, RetryState>(
  { attempt: 0, delay: 1000 },       // Anfangszustand: 1s
  state => state.attempt < 5,        // Maximal 5 Mal
  state => ({                        // Zustandsaktualisierung
    attempt: state.attempt + 1,
    delay: state.delay * 2           // Verzögerungszeit verdoppeln
  }),
  state => state.delay               // Verzögerungszeit emittieren
).subscribe(delay => {
  console.log(`Wiederholung nach ${delay / 1000} Sekunden`);
});

// Ausgabe:
// Wiederholung nach 1 Sekunden
// Wiederholung nach 2 Sekunden
// Wiederholung nach 4 Sekunden
// Wiederholung nach 8 Sekunden
// Wiederholung nach 16 Sekunden

3. Paginierungskontrolle

Abruf wird fortgesetzt, solange nächste Seite existiert.

typescript
import { generate, of, Observable, concatMap, delay } from 'rxjs';
interface PageState {
  page: number;
  hasNext: boolean;
}

interface PageData {
  page: number;
  items: string[];
  hasNext: boolean;
}

// Funktion zur Simulation des Seitenabrufs
function fetchPage(page: number): Observable<PageData> {
  return of({
    page,
    items: [`Element${page}-1`, `Element${page}-2`, `Element${page}-3`],
    hasNext: page < 10 // Bis Seite 10
  }).pipe(
    delay(500) // Simuliert API-Aufruf
  );
}

// Abruf solange Seite existiert (in Praxis hasNext aus API-Response holen)
generate<number, PageState>(
  { page: 1, hasNext: true },        // Anfangszustand
  state => state.hasNext,            // Solange nächste Seite existiert
  state => ({                        // Zustandsaktualisierung
    page: state.page + 1,
    hasNext: state.page < 10         // Angenommen bis Seite 10
  }),
  state => state.page                // Seitennummer emittieren
).pipe(
  concatMap(page => fetchPage(page)) // Jede Seite sequenziell abrufen
).subscribe(
  data => console.log(`Seite ${data.page} abgerufen:`, data.items),
  err => console.error('Fehler:', err),
  () => console.log('Alle Seiten abgerufen')
);

// Ausgabe:
// Seite 1 abgerufen: ['Element1-1', 'Element1-2', 'Element1-3']
// Seite 2 abgerufen: ['Element2-1', 'Element2-2', 'Element2-3']
// ...
// Seite 10 abgerufen: ['Element10-1', 'Element10-2', 'Element10-3']
// Alle Seiten abgerufen

4. Benutzerdefinierter Timer

Emittiert Ereignisse in unregelmäßigen Intervallen.

typescript
import { generate, of, concatMap, delay } from 'rxjs';
interface TimerState {
  count: number;
  delay: number;
}

// Timer mit graduell zunehmender Verzögerung
generate<string, TimerState>(
  { count: 0, delay: 1000 },         // Anfangszustand: 1s
  state => state.count < 5,          // Bis 5 Mal
  state => ({                        // Zustandsaktualisierung
    count: state.count + 1,
    delay: state.delay + 500         // Verzögerung um 500ms erhöhen
  }),
  state => `Ereignis${state.count + 1}`
).pipe(
  concatMap((message, index) => {
    const delayTime = 1000 + index * 500;
    console.log(`Emission nach ${delayTime}ms Wartezeit`);
    return of(message).pipe(delay(delayTime));
  })
).subscribe(console.log);

// Ausgabe:
// Emission nach 1000ms Wartezeit
// Ereignis1 (nach 1s)
// Emission nach 1500ms Wartezeit
// Ereignis2 (nach 2.5s)
// Emission nach 2000ms Wartezeit
// Ereignis3 (nach 4.5s)
// ...

5. Fakultätsberechnung

Mathematische Berechnungen als Stream ausdrücken.

typescript
import { generate } from 'rxjs';

interface FactorialState {
  n: number;
  result: number;
}

// Berechnet 5! (5! = 5 × 4 × 3 × 2 × 1 = 120)
generate<number, FactorialState>(
  { n: 5, result: 1 },               // Anfangszustand
  state => state.n > 0,              // Solange n > 0
  state => ({                        // Zustandsaktualisierung
    n: state.n - 1,
    result: state.result * state.n
  }),
  state => state.result              // Zwischenergebnisse emittieren
).subscribe(console.log);

// Ausgabe: 5, 20, 60, 120, 120
// (1*5, 5*4, 20*3, 60*2, 120*1)

Vergleich mit anderen Creation Functions

generate() vs range()

typescript
import { generate, range } from 'rxjs';

// range() - Einfache Sequenznummern
range(1, 5).subscribe(console.log);
// Ausgabe: 1, 2, 3, 4, 5

// generate() - Gleiche Funktionalität expliziter
generate(
  1,
  x => x <= 5,
  x => x + 1
).subscribe(console.log);
// Ausgabe: 1, 2, 3, 4, 5

// Wahre Stärke von generate(): Komplexe Schritte
generate(
  1,
  x => x <= 100,
  x => x * 2  // Verdoppelt bei jeder Iteration
).subscribe(console.log);
// Ausgabe: 1, 2, 4, 8, 16, 32, 64
// (mit range() nicht möglich)

generate() vs defer()

typescript
import { generate, defer, of } from 'rxjs';

// generate() - Schleifenverarbeitung
generate(1, x => x <= 3, x => x + 1).subscribe(console.log);
// Ausgabe: 1, 2, 3

// defer() - Generierung bei Subscription (keine Schleife)
defer(() => of(1, 2, 3)).subscribe(console.log);
// Ausgabe: 1, 2, 3

// Unterschied: generate() hat Zustand, defer nur verzögerte Auswertung

TIP

Auswahlkriterien:

  • Einfache Sequenznummernrange()
  • Komplexe Bedingungen oder Schrittegenerate()
  • Dynamische Entscheidung bei Subscriptiondefer()
  • Fibonacci, Fakultät etc.generate()

Asynchronisierung mit Scheduler

Bei der Verarbeitung großer Datenmengen kann asynchrone Ausführung durch Angabe eines Schedulers erfolgen.

typescript
import { generate, asyncScheduler, observeOn } from 'rxjs';
console.log('Start');

// 1 Million Schleifendurchläufe asynchron
generate(
  1,
  x => x <= 1000000,
  x => x + 1
).pipe(
  observeOn(asyncScheduler)
).subscribe({
  next: val => {
    if (val % 100000 === 0) {
      console.log(`Fortschritt: ${val}`);
    }
  },
  complete: () => console.log('Abgeschlossen')
});

console.log('Nach Subscription (wird sofort ausgeführt da asynchron)');

// Ausgabe:
// Start
// Nach Subscription (wird sofort ausgeführt da asynchron)
// Fortschritt: 100000
// Fortschritt: 200000
// ...
// Abgeschlossen

Performance-Hinweise

generate() emittiert Werte synchron, daher ist bei großen Datenmengen oder komplexen Berechnungen Vorsicht geboten.

WARNING

Performance-Optimierung:

typescript
// ❌ Schlechtes Beispiel: Komplexe Berechnung synchron (UI wird blockiert)
generate(
  1,
  x => x <= 1000000,
  x => expensiveCalculation(x)
).subscribe(console.log);

// ✅ Gutes Beispiel 1: Asynchronisierung mit Scheduler
generate(
  1,
  x => x <= 1000000,
  x => expensiveCalculation(x)
).pipe(
  observeOn(asyncScheduler)
).subscribe(console.log);

// ✅ Gutes Beispiel 2: Anzahl mit take() begrenzen
generate(
  1,
  x => true,  // Endlosschleife
  x => x + 1
).pipe(
  take(100)   // Nur erste 100
).subscribe(console.log);

Fehlerbehandlung

generate() selbst emittiert keine Fehler, aber in Pipeline oder Zustandsaktualisierungsfunktion können Fehler auftreten.

typescript
import { generate, of, map, catchError } from 'rxjs';
generate(
  1,
  x => x <= 10,
  x => x + 1
).pipe(
  map(n => {
    if (n === 5) {
      throw new Error('Fehler bei 5');
    }
    return n * 2;
  }),
  catchError(error => {
    console.error('Fehler aufgetreten:', error.message);
    return of(-1); // Standardwert zurückgeben
  })
).subscribe(console.log);

// Ausgabe: 2, 4, 6, 8, -1

Fehler in Zustandsaktualisierungsfunktion

Tritt ein Fehler in der Zustandsaktualisierungsfunktion auf, geht das Observable in Fehlerzustand.

typescript
import { generate, EMPTY, catchError } from 'rxjs';
generate(
  1,
  x => x <= 10,
  x => {
    if (x === 5) {
      throw new Error('Fehler bei Zustandsaktualisierung');
    }
    return x + 1;
  }
).pipe(
  catchError(error => {
    console.error('Fehler:', error.message);
    return EMPTY; // Leeres Observable zurückgeben
  })
).subscribe({
  next: console.log,
  complete: () => console.log('Abgeschlossen')
});

// Ausgabe: 1, 2, 3, 4, Fehler: Fehler bei Zustandsaktualisierung, Abgeschlossen

Typsicherheit mit TypeScript

generate() kann Zustandstyp und emittierten Werttyp trennen.

typescript
import { generate } from 'rxjs';

interface State {
  count: number;
  sum: number;
}

interface Result {
  index: number;
  average: number;
}

// Zustand: State, emittierter Wert: Result
const stats$ = generate<Result, State>(
  { count: 1, sum: 0 },
  state => state.count <= 5,
  state => ({
    count: state.count + 1,
    sum: state.sum + state.count
  }),
  state => ({
    index: state.count,
    average: state.sum / state.count
  })
);

stats$.subscribe(result => {
  console.log(`[${result.index}] Durchschnitt: ${result.average}`);
});

// Ausgabe:
// [1] Durchschnitt: 0
// [2] Durchschnitt: 0.5
// [3] Durchschnitt: 1
// [4] Durchschnitt: 1.5
// [5] Durchschnitt: 2

Zusammenfassung

generate() ist eine leistungsstarke Creation Function zur deklarativen Beschreibung komplexer Schleifenverarbeitungen.

IMPORTANT

Eigenschaften von generate():

  • ✅ Flexible Schleifenkontrolle wie while/for-Schleife
  • ✅ Komplexe Zustandsverwaltung möglich
  • ✅ Optimal für mathematische Berechnungen wie Fibonacci, Fakultät
  • ✅ Zustand und emittierter Wert können getrennt werden
  • ⚠️ Vorsicht vor Endlosschleifen (mit take() begrenzen)
  • ⚠️ Bei großen Datenmengen Asynchronisierung erwägen
  • ⚠️ Für einfache Sequenznummern range() verwenden

Verwandte Themen

  • range() - Einfache Sequenzgenerierung
  • defer() - Dynamische Generierung bei Subscription
  • expand() - Rekursive Expansion (Higher-Order-Operator)
  • scan() - Kumulative Berechnung

Referenzressourcen

Veröffentlicht unter CC-BY-4.0-Lizenz.