Async Iterables sauber in Arrays verwandeln
Wer asynchrone Datenströme bisher mit manuellen Schleifen und push-Aufrufen in ein Array überführt hat, kennt die Race-Conditions und die umständliche Fehlerbehandlung. Array.fromAsync löst genau dieses Problem: eine einzige eingebaute Methode, die Async Iterables, Generatoren und gemischte Promise-Listen kontrolliert und in der richtigen Reihenfolge in ein Array sammelt.
Inhaltsverzeichnis
- 1. Das Problem: Async Iterables manuell in Arrays sammeln
- 2. Array.fromAsync: Grundsyntax und Unterschied zu Array.from
- 3. Async Generatoren mit Array.fromAsync konsumieren
- 4. Mapping-Funktion: Transformation während des Sammelns
- 5. Praxisbeispiel: Paginierte API-Ergebnisse sammeln
- 6. Fehlerbehandlung bei Array.fromAsync
- 7. Gemischte Listen aus Werten und Promises
- 8. Sequentielle Verarbeitung, Reihenfolge und Performance
- 9. Array.fromAsync im Vergleich zu Alternativen
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem: Async Iterables manuell in Arrays sammeln
Bevor es Array.fromAsync gab, musste jeder asynchrone Datenstrom manuell in ein Array überführt werden. Der typische Ansatz: ein leeres Array anlegen, eine for await...of-Schleife über das Async Iterable laufen lassen und in jedem Durchlauf per push ein Element anhängen. Das funktioniert, ist aber Boilerplate, den man in jedem Projekt neu schreibt, und er verschleiert die eigentliche Absicht des Codes: aus einer asynchronen Quelle ein vollständiges Array zu erzeugen.
Das zweite Problem war die Fehlerbehandlung. Bricht die Iteration in der Mitte ab, weil ein Netzwerk-Request fehlschlägt, bleibt das teilweise gefüllte Array oft irgendwo hängen, ohne dass der Aufrufer sauber erkennt, wo genau der Abbruch passiert ist. Array.fromAsync kapselt diese Logik in einer einzigen eingebauten Methode, wirft bei Fehlern eine reguläre Exception und macht den Code an der Aufrufstelle deutlich lesbarer als eine manuell geschriebene Sammel-Schleife.
2. Array.fromAsync: Grundsyntax und Unterschied zu Array.from
Array.fromAsync ist das asynchrone Gegenstück zu Array.from. Während Array.from synchrone Iterables und Array-like-Objekte konsumiert, akzeptiert Array.fromAsync zusätzlich Async Iterables, also alles, was Symbol.asyncIterator implementiert. Der zentrale Unterschied: Array.fromAsync gibt immer ein Promise zurück, das erst aufgelöst wird, wenn alle Elemente eingesammelt wurden.
Die Signatur entspricht der von Array.from: erstes Argument die Quelle, optional eine Mapping-Funktion als zweites Argument, optional ein thisArg als drittes. Der entscheidende Vorteil gegenüber einer selbstgeschriebenen Schleife liegt in der Einheitlichkeit: Array.fromAsync funktioniert identisch für Async Generatoren, Streams und normale Arrays mit Promise-Werten, ohne dass man für jeden Fall eine eigene Sammel-Logik schreiben muss.
// Array.fromAsync consumes any async iterable
async function* fetchPages() {
yield "page-1";
yield "page-2";
yield "page-3";
}
const pages = await Array.fromAsync(fetchPages());
console.log(pages); // ["page-1", "page-2", "page-3"]
// Compare: the manual pattern before Array.fromAsync existed
async function collectManually(source) {
const result = [];
for await (const item of source) {
result.push(item);
}
return result;
}
3. Async Generatoren mit Array.fromAsync konsumieren
Async Generatoren sind die häufigste Quelle für Array.fromAsync. Ein Async Generator liefert seine Werte nacheinander mit yield, wobei zwischen den Werten beliebig lange asynchrone Operationen wie Netzwerk-Requests oder Datenbankabfragen stattfinden können. Array.fromAsync wartet bei jedem yield, bis der nächste Wert bereitsteht, und sammelt am Ende alle Werte in der Reihenfolge, in der sie erzeugt wurden.
Das ist besonders praktisch, wenn eine Datenquelle intern lazy arbeitet, also Werte erst bei Bedarf berechnet. Ein Generator, der Zeilen aus einer Datei liest oder Einträge aus einer Datenbank-Cursor-Iteration liefert, muss seine interne Logik nicht ändern, um mit Array.fromAsync zusammenzuarbeiten. Solange das Objekt Symbol.asyncIterator korrekt implementiert, funktioniert die Sammlung ohne Anpassung.
4. Mapping-Funktion: Transformation während des Sammelns
Wie bei Array.from akzeptiert auch Array.fromAsync eine optionale Mapping-Funktion als zweites Argument. Diese Funktion kann selbst synchron oder asynchron sein. Ist die Mapping-Funktion asynchron, wartet Array.fromAsync auf ihre Auflösung, bevor der nächste Wert aus der Quelle angefordert wird. Das erlaubt, Rohdaten direkt beim Einsammeln zu transformieren, statt in einem zweiten Schritt eine separate map-Iteration durchzuführen.
Ein typischer Anwendungsfall: eine Liste von IDs wird asynchron eingesammelt, und die Mapping-Funktion lädt für jede ID die zugehörigen Detaildaten nach. Ohne Array.fromAsync müsste man zunächst alle IDs sammeln und danach in einer zweiten Promise.all-Runde die Details laden. Mit der eingebauten Mapping-Funktion passiert beides in einem einzigen, klar lesbaren Ausdruck.
async function* userIds() {
yield 1;
yield 2;
yield 3;
}
async function fetchUserDetail(id) {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
// Mapping function runs for each yielded value, awaited in order
const users = await Array.fromAsync(userIds(), async (id) => fetchUserDetail(id));
console.log(users.length); // 3
5. Praxisbeispiel: Paginierte API-Ergebnisse sammeln
Paginierte REST-APIs sind ein klassisches Einsatzgebiet für Array.fromAsync. Statt eine while-Schleife mit manueller Cursor-Verwaltung zu schreiben, kapselt man die Paginierungslogik in einem Async Generator, der Seite für Seite lädt und einzelne Elemente per yield* weiterreicht. Array.fromAsync übernimmt danach nur noch das Einsammeln, während der Generator die eigentliche Netzwerk-Logik isoliert hält.
Dieses Muster trennt sauber zwei Verantwortlichkeiten: der Generator weiß, wie man von Seite zu Seite navigiert, und der Aufrufer entscheidet nur, ob er alle Ergebnisse als Array braucht oder lieber selbst mit for await iteriert. Dieselbe Generator-Funktion lässt sich damit sowohl für Array.fromAsync als auch für speicherschonende Streaming-Verarbeitung wiederverwenden, ohne Code zu duplizieren.
async function* paginatedResults(baseUrl) {
let url = baseUrl;
while (url) {
const res = await fetch(url);
const data = await res.json();
yield* data.items; // yield each item individually
url = data.nextPageUrl; // null ends the generator
}
}
// Collect every item across all pages into one array
const allItems = await Array.fromAsync(paginatedResults("/api/products?page=1"));
console.log(`Loaded ${allItems.length} products across all pages`);
6. Fehlerbehandlung bei Array.fromAsync
Wirft die Quelle während der Iteration einen Fehler, egal ob im Generator selbst oder in einer awaiteten Netzwerk-Operation, lehnt das von Array.fromAsync zurückgegebene Promise ab. Der Aufrufer fängt diesen Fehler mit einem gewöhnlichen try/catch um den await-Ausdruck ab, genau wie bei jedem anderen Promise. Bereits gesammelte Elemente gehen dabei verloren, weil Array.fromAsync kein Teil-Ergebnis zurückgibt, sondern nur ein vollständiges Array oder eine Ablehnung kennt.
Wer Teilresultate auch bei Fehlern behalten möchte, muss die Sammlung selbst mit for await implementieren und Fehler pro Element behandeln, statt Array.fromAsync zu nutzen. Für die meisten Anwendungsfälle, etwa das Laden einer abgeschlossenen Ergebnisliste, ist das Alles-oder-nichts-Verhalten aber genau richtig: ein unvollständiges Array wäre ohnehin selten sinnvoll weiterzuverarbeiten.
async function* riskySource() {
yield 1;
yield 2;
throw new Error("Network timeout on item 3");
}
try {
const result = await Array.fromAsync(riskySource());
console.log(result);
} catch (err) {
// The rejection carries the original error, no partial array is returned
console.error("Collection failed:", err.message);
}
7. Gemischte Listen aus Werten und Promises
Neben echten Async Iterables akzeptiert Array.fromAsync auch gewöhnliche synchrone Iterables, deren Elemente selbst Promises sind. Das ist ein Unterschied zu Promise.all(array): Array.fromAsync löst die Promises nacheinander auf, während Promise.all alle gleichzeitig startet. Enthält die Quelle eine Mischung aus normalen Werten und Promises, werden Promises awaited und normale Werte unverändert übernommen.
Dieses Verhalten ist wichtig zu verstehen, bevor man Array.fromAsync als Ersatz für Promise.all einsetzt: bei vielen unabhängigen Promises, die parallel laufen sollen, bleibt Promise.all die richtige Wahl, weil es alle Operationen gleichzeitig anstößt. Array.fromAsync ist dagegen die richtige Wahl, wenn die Reihenfolge der Verarbeitung von Bedeutung ist oder die Quelle selbst ein echtes Async Iterable mit eigener Zeitsteuerung ist.
8. Sequentielle Verarbeitung, Reihenfolge und Performance
Ein häufiges Missverständnis: Array.fromAsync parallelisiert nichts von selbst. Jeder Wert wird der Reihe nach angefordert, awaited und erst danach wird der nächste Wert von der Quelle verlangt. Für einen Async Generator, der pro yield einen eigenen Netzwerk-Request macht, bedeutet das: die Requests laufen sequentiell, nicht parallel. Wer echte Parallelität braucht, muss die Requests selbst mit Promise.all parallelisieren und das Ergebnis-Array danach optional mit Array.fromAsync weiterverarbeiten.
Für viele Praxisfälle ist die sequentielle Natur aber genau erwünscht: bei paginierten APIs etwa muss die nächste Seite ohnehin erst angefragt werden, wenn die aktuelle Seite den Cursor für die nächste liefert. Hier passt das sequentielle Verhalten von Array.fromAsync exakt zur Natur des Problems, ohne dass man künstliche Parallelität erzwingen müsste, die bei diesem Anwendungsfall ohnehin nicht möglich wäre.
9. Array.fromAsync im Vergleich zu Alternativen
Die Wahl zwischen Array.fromAsync, einer manuellen for await-Schleife und Promise.all hängt von der konkreten Datenquelle und dem gewünschten Nebenläufigkeitsverhalten ab. Die folgende Tabelle fasst zusammen, wann welches Pattern die bessere Wahl ist.
| Aufgabe | Ohne Array.fromAsync | Mit Array.fromAsync | Vorteil |
|---|---|---|---|
| Async Generator sammeln | for await + push |
Array.fromAsync(gen()) |
Weniger Boilerplate, klarere Absicht |
| Transformation beim Sammeln | Sammeln, dann separates map |
Mapping-Funktion als 2. Argument | Ein Durchlauf statt zwei |
| Parallele Promises | Promise.all(array) |
Array.fromAsync (sequentiell) |
Promise.all bleibt hier vorteilhaft |
| Paginierte API | Manuelle while-Cursor-Schleife |
Generator + Array.fromAsync | Logik getrennt, wiederverwendbar |
| Fehler mitten in der Iteration | Muss selbst behandelt werden | Promise-Rejection automatisch | Einheitliches Fehlerverhalten |
In der Praxis ergänzen sich diese Ansätze, statt sich gegenseitig zu ersetzen: Array.fromAsync übernimmt das Einsammeln aus einer sequentiellen Quelle, Promise.all bleibt für echte Parallelität zuständig, und eine manuelle Schleife ist nur noch dort nötig, wo Teilresultate auch bei einem Fehler erhalten bleiben müssen.
Mironsoft
Moderne JavaScript-Entwicklung und Frontend-Architektur
Async-Datenflüsse, die zuverlässig und lesbar bleiben?
Wir modernisieren bestehenden JavaScript-Code, ersetzen fragile manuelle Sammel-Schleifen durch Array.fromAsync und andere ES2024-Patterns und sorgen für sauberes Fehlerverhalten in euren Frontend- und Node-Anwendungen.
Code-Modernisierung
Legacy-Schleifen durch moderne Array- und Async-Patterns ersetzen
API-Integration
Paginierte Schnittstellen sauber mit Async Generatoren anbinden
Code-Review
Fehlerbehandlung und Nebenläufigkeit in bestehendem Code prüfen
10. Zusammenfassung
Array.fromAsync ersetzt eine der häufigsten manuell geschriebenen Schleifen im asynchronen JavaScript: das Einsammeln von Werten aus einem Async Iterable in ein gewöhnliches Array. Statt for await...of mit manuellem push zu kombinieren, übernimmt eine einzige eingebaute Methode das Warten auf jeden Wert, die optionale Transformation über eine Mapping-Funktion und die Fehlerbehandlung über eine reguläre Promise-Rejection.
Wichtig ist das Verständnis, dass Array.fromAsync sequentiell arbeitet und keine automatische Parallelität bietet. Für echte parallele Verarbeitung bleibt Promise.all die richtige Wahl, während Array.fromAsync überall dort glänzt, wo Reihenfolge zählt oder die Quelle selbst ein Async Generator mit eigener Zeitsteuerung ist, etwa bei paginierten APIs, Datei-Streams oder Datenbank-Cursorn.
Array.fromAsync — Das Wichtigste auf einen Blick
Grundfunktion
Sammelt Async Iterables, Async Generatoren und Promise-Listen in ein gewöhnliches Array. Gibt selbst ein Promise zurück.
Mapping-Funktion
Zweites Argument transformiert jeden Wert direkt beim Sammeln, synchron oder asynchron, spart einen zweiten Durchlauf.
Reihenfolge & Performance
Arbeitet sequentiell. Für parallele Promises bleibt Promise.all die passendere Wahl.
Fehlerbehandlung
Ein Fehler während der Iteration führt zu einer Promise-Rejection, kein Teil-Array wird zurückgegeben.