Arbeiten mit Aufgabenwarteschlangen in TypeScript: Eine vollständige Anleitung
In der modernen Webentwicklung und bei Serveranwendungen besteht häufig die Notwendigkeit, Aufgaben asynchron, im Hintergrund oder mit Parallelitätsbeschränkungen auszuführen. Aufgabenwarteschlangen (task queues) sind ein leistungsstarkes Muster, das eine vorhersagbare und effiziente Ausführung von Operationen ermöglicht. In diesem Artikel werden wir im Detail besprechen, wie man Aufgabenwarteschlangen in TypeScript erstellt und verwendet, von einfachen Implementierungen bis hin zu fortgeschrittenen Techniken mit Prioritäten und Fehlerbehandlung.
Was ist eine Aufgabenwarteschlange und warum wird sie benötigt?
Eine Aufgabenwarteschlange ist eine Datenstruktur, die nach dem FIFO-Prinzip (First In, First Out) funktioniert. Aufgaben werden am Ende der Warteschlange hinzugefügt und vom Anfang entnommen. Im Kontext von TypeScript und Node.js werden Warteschlangen verwendet für:
- Parallelitätsbegrenzung — Kontrolle der Anzahl gleichzeitig ausgeführter Operationen (z. B. API-Anfragen mit Limits).
- Asynchrone Verarbeitung — Ausführung schwerer Aufgaben im Hintergrund ohne Blockierung des Hauptthreads.
- Wiederholungsversuche — Automatischer Neustart fehlgeschlagener Aufgaben mit Verzögerung.
- Reihenfolgeverwaltung — Garantie der sequentiellen Ausführung abhängiger Operationen.
TypeScript fügt strenge Typisierung hinzu, was den Code von Warteschlangen zuverlässiger und vorhersagbarer macht.
Basisimplementierung einer Aufgabenwarteschlange in TypeScript
Beginnen wir mit der Erstellung einer einfachen, aber vollständig typisierten Warteschlange. Sie nimmt Aufgaben (Funktionen, die ein Promise zurückgeben) an und führt sie sequentiell aus.
// Definieren des Aufgabentypstype Task<T> = () => Promise<T>;
// Klasse der Warteschlangeclass TaskQueue<T> { private queue: Task<T>[] = []; private isProcessing: boolean = false;
// Hinzufügen einer Aufgabe zur Warteschlange enqueue(task: Task<T>): void { this.queue.push(task); this.process(); }
// Starten der Verarbeitung private async process(): Promise<void> { if (this.isProcessing) return; this.isProcessing = true;
while (this.queue.length > 0) { const task = this.queue.shift()!; try { const result = await task(); console.log('Aufgabe ausgeführt:', result); } catch (error) { console.error('Fehler bei der Aufgabenausführung:', error); } }
this.isProcessing = false; }}
// Beispiel zur Verwendungconst queue = new TaskQueue<string>();queue.enqueue(async () => { await new Promise(resolve => setTimeout(resolve, 1000)); return 'Erste Aufgabe';});queue.enqueue(async () => 'Zweite Aufgabe (schnell)');Diese Implementierung garantiert die sequentielle Ausführung und den automatischen Start der Verarbeitung beim Hinzufügen einer neuen Aufgabe. Sie unterstützt jedoch keine Parallelität und Prioritäten.
Warteschlange mit Parallelitätsbegrenzung
Häufig ist es erforderlich, mehrere Aufgaben gleichzeitig auszuführen, aber ein bestimmtes Limit nicht zu überschreiten (z. B. 5 parallele Anfragen). Fügen wir den Parameter concurrency hinzu.
class ParallelTaskQueue<T> { private queue: Task<T>[] = []; private activeCount: number = 0; private readonly concurrency: number;
constructor(concurrency: number = 3) { this.concurrency = concurrency; }
enqueue(task: Task<T>): void { this.queue.push(task); this.processNext(); }
private processNext(): void { while (this.activeCount < this.concurrency && this.queue.length > 0) { const task = this.queue.shift()!; this.activeCount++; task() .then(result => console.log('Erfolg:', result)) .catch(error => console.error('Fehler:', error)) .finally(() => { this.activeCount--; this.processNext(); }); } }}