Entfesseln Sie die Leistung der parallelen Verarbeitung in JavaScript. Lernen Sie, nebenlĂ€ufige Promises mit Promise.all, allSettled, race und any fĂŒr schnellere, robustere Anwendungen zu verwalten.
JavaScript-NebenlÀufigkeit meistern: Ein tiefer Einblick in die parallele Verarbeitung von Promises
In der Landschaft der modernen Webentwicklung ist Leistung kein Feature, sondern eine grundlegende Anforderung. Benutzer auf der ganzen Welt erwarten, dass Anwendungen schnell, reaktionsschnell und nahtlos sind. Im Zentrum dieser Leistungsherausforderung, insbesondere in JavaScript, liegt das Konzept der effizienten Handhabung asynchroner Operationen. Vom Abrufen von Daten von einer API ĂŒber das Lesen einer Datei bis hin zur Abfrage einer Datenbank gibt es viele Aufgaben, die nicht sofort abgeschlossen werden. Wie wir diese Wartezeiten verwalten, kann den Unterschied zwischen einer trĂ€gen Anwendung und einem erfreulich flĂŒssigen Benutzererlebnis ausmachen.
JavaScript ist von Natur aus eine single-threaded Sprache. Das bedeutet, dass es immer nur einen Codeabschnitt zur gleichen Zeit ausfĂŒhren kann. Das mag wie eine EinschrĂ€nkung klingen, aber der Event-Loop und das nicht-blockierende I/O-Modell von JavaScript ermöglichen es, asynchrone Aufgaben mit unglaublicher Effizienz zu bewĂ€ltigen. Der moderne Eckpfeiler dieses Modells ist das Promise â ein Objekt, das den letztendlichen Abschluss (oder das Scheitern) einer asynchronen Operation darstellt.
Die bloĂe Verwendung von Promises oder ihrer eleganten `async/await`-Syntax garantiert jedoch nicht automatisch eine optimale Leistung. Eine hĂ€ufige Falle fĂŒr Entwickler ist die sequentielle Behandlung mehrerer unabhĂ€ngiger asynchroner Aufgaben, was zu unnötigen EngpĂ€ssen fĂŒhrt. Hier kommt die nebenlĂ€ufige Verarbeitung von Promises ins Spiel. Indem wir mehrere asynchrone Operationen parallel starten und gemeinsam auf sie warten, können wir die GesamtausfĂŒhrungszeit drastisch reduzieren und weitaus effizientere Anwendungen erstellen.
Dieser umfassende Leitfaden nimmt Sie mit auf einen tiefen Einblick in die Welt der JavaScript-NebenlĂ€ufigkeit. Wir werden die direkt in die Sprache integrierten Werkzeuge â `Promise.all()`, `Promise.allSettled()`, `Promise.race()` und `Promise.any()` â erkunden, um Ihnen zu helfen, parallele Aufgaben wie ein Profi zu orchestrieren. Ob Sie ein Junior-Entwickler sind, der sich mit AsynchronitĂ€t vertraut macht, oder ein erfahrener Ingenieur, der seine Muster verfeinern möchte, dieser Artikel wird Sie mit dem Wissen ausstatten, um schnelleren, widerstandsfĂ€higeren und anspruchsvolleren JavaScript-Code zu schreiben.
Zuerst eine kurze KlÀrung: NebenlÀufigkeit vs. ParallelitÀt
Bevor wir fortfahren, ist es wichtig, zwei Begriffe zu klÀren, die oft synonym verwendet werden, aber in der Informatik unterschiedliche Bedeutungen haben: NebenlÀufigkeit und ParallelitÀt.
- NebenlĂ€ufigkeit (Concurrency) ist das Konzept der Verwaltung mehrerer Aufgaben ĂŒber einen bestimmten Zeitraum. Es geht darum, viele Dinge gleichzeitig zu bewĂ€ltigen. Ein System ist nebenlĂ€ufig, wenn es mehr als eine Aufgabe starten, ausfĂŒhren und abschlieĂen kann, ohne auf das Ende der vorherigen zu warten. In der single-threaded Umgebung von JavaScript wird NebenlĂ€ufigkeit ĂŒber den Event-Loop erreicht, der es der Engine ermöglicht, zwischen Aufgaben zu wechseln. WĂ€hrend eine lang andauernde Aufgabe (wie eine Netzwerkanfrage) wartet, kann die Engine an anderen Dingen arbeiten.
- ParallelitĂ€t (Parallelism) ist das Konzept der gleichzeitigen AusfĂŒhrung mehrerer Aufgaben. Es geht darum, viele Dinge gleichzeitig zu tun. Echte ParallelitĂ€t erfordert einen Mehrkernprozessor, bei dem verschiedene Threads auf verschiedenen Kernen zur exakt gleichen Zeit laufen können. WĂ€hrend Web Worker echte ParallelitĂ€t im browserbasierten JavaScript ermöglichen, bezieht sich das Kernmodell der NebenlĂ€ufigkeit, das wir hier diskutieren, auf den einzelnen Hauptthread.
FĂŒr I/O-gebundene Operationen (wie Netzwerkanfragen) bietet das nebenlĂ€ufige Modell von JavaScript den *Effekt* von ParallelitĂ€t. Wir können mehrere Anfragen gleichzeitig initiieren. WĂ€hrend die JavaScript-Engine auf die Antworten wartet, ist sie frei, andere Arbeiten zu erledigen. Die Operationen finden aus der Perspektive der externen Ressourcen (Server, Dateisysteme) 'parallel' statt. Dies ist das leistungsstarke Modell, das wir nutzen werden.
Die sequentielle Falle: Ein hÀufiges Anti-Pattern
Beginnen wir damit, einen hĂ€ufigen Fehler zu identifizieren. Wenn Entwickler `async/await` zum ersten Mal lernen, ist die Syntax so sauber, dass es leicht ist, Code zu schreiben, der synchron aussieht, aber unbeabsichtigt sequentiell und ineffizient ist. Stellen Sie sich vor, Sie mĂŒssen das Profil eines Benutzers, seine letzten BeitrĂ€ge und seine Benachrichtigungen abrufen, um ein Dashboard zu erstellen.
Ein naiver Ansatz könnte so aussehen:
Beispiel: Der ineffiziente sequentielle Abruf
async function fetchDashboardDataSequentially(userId) {
console.time('sequentialFetch');
console.log('Fetching user profile...');
const userProfile = await fetchUserProfile(userId); // Waits here
console.log('Fetching user posts...');
const userPosts = await fetchUserPosts(userId); // Waits here
console.log('Fetching user notifications...');
const userNotifications = await fetchUserNotifications(userId); // Waits here
console.timeEnd('sequentialFetch');
return { userProfile, userPosts, userNotifications };
}
// Imagine these functions take time to resolve
// fetchUserProfile -> 500ms
// fetchUserPosts -> 800ms
// fetchUserNotifications -> 1000ms
Was ist an diesem Bild falsch? Jedes `await`-SchlĂŒsselwort pausiert die AusfĂŒhrung der `fetchDashboardDataSequentially`-Funktion, bis das Promise aufgelöst ist. Die Anfrage fĂŒr `userPosts` startet nicht einmal, bis die `userProfile`-Anfrage vollstĂ€ndig abgeschlossen ist. Die Anfrage fĂŒr `userNotifications` startet nicht, bis `userPosts` zurĂŒck ist. Diese drei Netzwerkanfragen sind voneinander unabhĂ€ngig; es gibt keinen Grund zu warten! Die Gesamtzeit ist die Summe aller einzelnen Zeiten:
Gesamtzeit â 500ms + 800ms + 1000ms = 2300ms
Das ist ein riesiger Leistungsengpass. Wir können das viel, viel besser machen.
Leistungssteigerung: Die Kraft der nebenlĂ€ufigen AusfĂŒhrung
Die Lösung besteht darin, alle asynchronen Operationen auf einmal zu initiieren, ohne sofort auf sie zu warten. Dadurch können sie nebenlĂ€ufig ausgefĂŒhrt werden. Wir können die ausstehenden Promise-Objekte in Variablen speichern und dann einen Promise-Kombinator verwenden, um auf deren Abschluss zu warten.
Beispiel: Der effiziente nebenlÀufige Abruf
async function fetchDashboardDataConcurrently(userId) {
console.time('concurrentFetch');
console.log('Initiating all fetches at once...');
const profilePromise = fetchUserProfile(userId);
const postsPromise = fetchUserPosts(userId);
const notificationsPromise = fetchUserNotifications(userId);
// Now we wait for all of them to complete
const [userProfile, userPosts, userNotifications] = await Promise.all([
profilePromise,
postsPromise,
notificationsPromise
]);
console.timeEnd('concurrentFetch');
return { userProfile, userPosts, userNotifications };
}
In dieser Version rufen wir die drei Abruffunktionen ohne `await` auf. Dadurch werden alle drei Netzwerkanfragen sofort gestartet. Die JavaScript-Engine ĂŒbergibt sie an die zugrunde liegende Umgebung (den Browser oder Node.js) und erhĂ€lt drei ausstehende Promises zurĂŒck. Dann wird `Promise.all()` verwendet, um auf die Auflösung aller drei Promises zu warten. Die Gesamtzeit wird nun durch die am lĂ€ngsten laufende Operation bestimmt, nicht durch die Summe.
Gesamtzeit â max(500ms, 800ms, 1000ms) = 1000ms
Wir haben gerade unsere Datenabrufzeit um mehr als die HÀlfte reduziert! Das ist das Grundprinzip der parallelen Promise-Verarbeitung. Lassen Sie uns nun die leistungsstarken Werkzeuge erkunden, die JavaScript zur Orchestrierung dieser nebenlÀufigen Aufgaben bietet.
Das Promise-Kombinatoren-Toolkit: `all`, `allSettled`, `race` und `any`
JavaScript bietet vier statische Methoden auf dem `Promise`-Objekt, die als Promise-Kombinatoren bekannt sind. Jede nimmt ein iterierbares Objekt (wie ein Array) von Promises entgegen und gibt ein einziges neues Promise zurĂŒck. Das Verhalten dieses neuen Promises hĂ€ngt davon ab, welchen Kombinator Sie verwenden.
1. `Promise.all()`: Der Alles-oder-Nichts-Ansatz
Promise.all() ist das perfekte Werkzeug, wenn Sie eine Gruppe von Aufgaben haben, die alle fĂŒr den nĂ€chsten Schritt entscheidend sind. Es reprĂ€sentiert die logische âUNDâ-Bedingung: Aufgabe 1 UND Aufgabe 2 UND Aufgabe 3 mĂŒssen alle erfolgreich sein.
- Eingabe: Ein iterierbares Objekt von Promises.
- Verhalten: Es gibt ein einziges Promise zurĂŒck, das erfĂŒllt wird, wenn alle Eingabe-Promises erfĂŒllt wurden. Der erfĂŒllte Wert ist ein Array der Ergebnisse der Eingabe-Promises in derselben Reihenfolge.
- Fehlermodus: Es wird sofort zurĂŒckgewiesen, sobald eines der Eingabe-Promises zurĂŒckgewiesen wird. Der ZurĂŒckweisungsgrund ist der Grund des ersten zurĂŒckgewiesenen Promises. Dies wird oft als âFail-Fastâ-Verhalten bezeichnet.
Anwendungsfall: Kritische Datenaggregation
Unser Dashboard-Beispiel ist ein perfekter Anwendungsfall. Wenn Sie das Profil des Benutzers nicht laden können, macht es möglicherweise keinen Sinn, seine BeitrĂ€ge und Benachrichtigungen anzuzeigen. Die gesamte Komponente hĂ€ngt davon ab, dass alle drei Datenpunkte verfĂŒgbar sind.
// Helper to simulate API calls
const mockApiCall = (value, delay, shouldFail = false) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(new Error(`API call failed for: ${value}`));
} else {
console.log(`Resolved: ${value}`);
resolve({ data: value });
}
}, delay);
});
};
async function loadCriticalData() {
console.log('Using Promise.all for critical data...');
try {
const [profile, settings, permissions] = await Promise.all([
mockApiCall('userProfile', 400),
mockApiCall('userSettings', 700),
mockApiCall('userPermissions', 500)
]);
console.log('All critical data loaded successfully!');
// Now render the UI with profile, settings, and permissions
} catch (error) {
console.error('Failed to load critical data:', error.message);
// Show an error message to the user
}
}
// What happens if one fails?
async function loadCriticalDataWithFailure() {
console.log('\nDemonstrating Promise.all failure...');
try {
const results = await Promise.all([
mockApiCall('userProfile', 400),
mockApiCall('userSettings', 700, true), // This one will fail
mockApiCall('userPermissions', 500)
]);
} catch (error) {
console.error('Promise.all rejected:', error.message);
// Note: The 'userProfile' and 'userPermissions' calls may have completed,
// but their results are lost because the whole operation failed.
}
}
loadCriticalData();
// After a delay, call the failure example
setTimeout(loadCriticalDataWithFailure, 2000);
Fallstrick bei `Promise.all()`
Der primĂ€re Fallstrick ist sein Fail-Fast-Charakter. Wenn Sie Daten fĂŒr zehn verschiedene, unabhĂ€ngige Widgets auf einer Seite abrufen und eine API fehlschlĂ€gt, wird `Promise.all()` zurĂŒckgewiesen, und Sie verlieren die Ergebnisse der anderen neun erfolgreichen Aufrufe. Hier glĂ€nzt unser nĂ€chster Kombinator.
2. `Promise.allSettled()`: Der resiliente Sammler
EingefĂŒhrt in ES2020, war `Promise.allSettled()` ein Wendepunkt fĂŒr die WiderstandsfĂ€higkeit. Es ist dafĂŒr konzipiert, wenn Sie das Ergebnis jedes einzelnen Promises wissen möchten, egal ob es erfolgreich war oder fehlgeschlagen ist. Es wird niemals zurĂŒckgewiesen.
- Eingabe: Ein iterierbares Objekt von Promises.
- Verhalten: Es gibt ein einziges Promise zurĂŒck, das immer erfĂŒllt wird. Es wird erfĂŒllt, sobald alle Eingabe-Promises abgeschlossen (entweder erfĂŒllt oder zurĂŒckgewiesen) sind. Der erfĂŒllte Wert ist ein Array von Objekten, die jeweils das Ergebnis eines Promises beschreiben.
- Ergebnisformat: Jedes Ergebnisobjekt hat eine `status`-Eigenschaft.
- Wenn erfĂŒllt: `{ status: 'fulfilled', value: theResult }`
- Wenn zurĂŒckgewiesen: `{ status: 'rejected', reason: theError }`
Anwendungsfall: Nicht-kritische, unabhÀngige Operationen
Stellen Sie sich eine Seite vor, die mehrere unabhĂ€ngige Komponenten anzeigt: ein Wetter-Widget, einen News-Feed und einen Börsenticker. Wenn die News-Feed-API fehlschlĂ€gt, möchten Sie trotzdem das Wetter und die Börseninformationen anzeigen. `Promise.allSettled()` ist dafĂŒr perfekt geeignet.
async function loadDashboardWidgets() {
console.log('\nUsing Promise.allSettled for independent widgets...');
const results = await Promise.allSettled([
mockApiCall('Weather Data', 600),
mockApiCall('News Feed', 1200, true), // This API is down
mockApiCall('Stock Ticker', 800)
]);
console.log('All promises have settled. Processing results...');
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Widget ${index} loaded successfully with data:`, result.value.data);
// Render this widget to the UI
} else {
console.error(`Widget ${index} failed to load:`, result.reason.message);
// Show a specific error state for this widget
}
});
}
loadDashboardWidgets();
Mit `Promise.allSettled()` wird Ihre Anwendung viel robuster. Ein einzelner Fehlerpunkt fĂŒhrt nicht zu einer Kaskade, die die gesamte BenutzeroberflĂ€che lahmlegt. Sie können jedes Ergebnis elegant behandeln.
3. `Promise.race()`: Der Erste im Ziel
Promise.race()` macht genau das, was sein Name andeutet. Es lĂ€sst eine Gruppe von Promises gegeneinander antreten und erklĂ€rt einen Gewinner, sobald der erste die Ziellinie ĂŒberquert, unabhĂ€ngig davon, ob es ein Erfolg oder ein Misserfolg war.
- Eingabe: Ein iterierbares Objekt von Promises.
- Verhalten: Es gibt ein einziges Promise zurĂŒck, das sich abschlieĂt (erfĂŒllt oder zurĂŒckweist), sobald das erste der Eingabe-Promises sich abschlieĂt. Der ErfĂŒllungswert oder ZurĂŒckweisungsgrund des zurĂŒckgegebenen Promises ist der des âgewinnendenâ Promises.
- Wichtiger Hinweis: Die anderen Promises werden nicht abgebrochen. Sie laufen im Hintergrund weiter, und ihre Ergebnisse werden vom `Promise.race()`-Kontext einfach ignoriert.
Anwendungsfall: Implementierung eines Timeouts
Der hĂ€ufigste und praktischste Anwendungsfall fĂŒr `Promise.race()` ist die Durchsetzung eines Timeouts fĂŒr eine asynchrone Operation. Sie können Ihre Hauptoperation gegen ein `setTimeout`-Promise âantretenâ lassen. Wenn Ihre Operation zu lange dauert, wird das Timeout-Promise zuerst abgeschlossen, und Sie können es als Fehler behandeln.
function createTimeout(delay) {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Operation timed out after ${delay}ms`));
}, delay);
});
}
async function fetchDataWithTimeout() {
console.log('\nUsing Promise.race for a timeout...');
try {
const result = await Promise.race([
mockApiCall('some critical data', 2000), // This will take too long
createTimeout(1500) // This will win the race
]);
console.log('Data fetched successfully:', result.data);
} catch (error) {
console.error(error.message);
}
}
fetchDataWithTimeout();
Ein weiterer Anwendungsfall: Redundante Endpunkte
Sie könnten `Promise.race()` auch verwenden, um mehrere redundante Server fĂŒr dieselbe Ressource abzufragen und die Antwort des schnellsten Servers zu nehmen. Dies ist jedoch riskant, denn wenn der schnellste Server einen Fehler zurĂŒckgibt (z.B. einen 500-Statuscode), wird `Promise.race()` sofort zurĂŒckgewiesen, selbst wenn ein etwas langsamerer Server eine erfolgreiche Antwort zurĂŒckgegeben hĂ€tte. Dies fĂŒhrt uns zu unserem letzten, fĂŒr dieses Szenario besser geeigneten Kombinator.
4. `Promise.any()`: Der Erste, der erfolgreich ist
EingefĂŒhrt in ES2021, ist `Promise.any()` wie eine optimistischere Version von `Promise.race()`. Es wartet auch auf das erste abzuschlieĂende Promise, sucht aber speziell nach dem ersten, das erfĂŒllt wird.
- Eingabe: Ein iterierbares Objekt von Promises.
- Verhalten: Es gibt ein einziges Promise zurĂŒck, das erfĂŒllt wird, sobald eines der Eingabe-Promises erfĂŒllt wird. Der ErfĂŒllungswert ist der Wert des ersten erfĂŒllten Promises.
- Fehlermodus: Es wird nur zurĂŒckgewiesen, wenn alle Eingabe-Promises zurĂŒckgewiesen werden. Der ZurĂŒckweisungsgrund ist ein spezielles `AggregateError`-Objekt, das eine `errors`-Eigenschaft enthĂ€lt â ein Array aller einzelnen ZurĂŒckweisungsgrĂŒnde.
Anwendungsfall: Abrufen von redundanten Quellen
Dies ist das perfekte Werkzeug zum Abrufen einer Ressource aus mehreren Quellen, wie primÀren und Backup-Servern oder mehreren Content Delivery Networks (CDNs). Sie möchten nur so schnell wie möglich eine erfolgreiche Antwort erhalten.
async function fetchResourceFromMirrors() {
console.log('\nUsing Promise.any to find the fastest successful source...');
try {
const resource = await Promise.any([
mockApiCall('Primary CDN', 800, true), // Fails quickly
mockApiCall('European Mirror', 1200), // Slower but will succeed
mockApiCall('Asian Mirror', 1100) // Also succeeds, but is slower than the European one
]);
console.log('Resource fetched successfully from a mirror:', resource.data);
} catch (error) {
if (error instanceof AggregateError) {
console.error('All mirrors failed to provide the resource.');
// You can inspect individual errors:
error.errors.forEach(err => console.log('- ' + err.message));
}
}
}
fetchResourceFromMirrors();
In diesem Beispiel ignoriert `Promise.any()` den schnellen Fehlschlag des primĂ€ren CDN und wartet darauf, dass der europĂ€ische Spiegel erfĂŒllt wird. An diesem Punkt wird es mit diesen Daten aufgelöst und das Ergebnis des asiatischen Spiegels effektiv ignoriert.
Das richtige Werkzeug fĂŒr die Aufgabe wĂ€hlen: Eine Kurzanleitung
Bei vier leistungsstarken Optionen, wie entscheiden Sie, welche Sie verwenden sollen? Hier ist ein einfaches Entscheidungs-Framework:
- Benötige ich die Ergebnisse ALLER Promises, und ist es eine Katastrophe, wenn EINES von ihnen fehlschlÀgt?
Verwenden SiePromise.all(). Dies ist fĂŒr eng gekoppelte Alles-oder-Nichts-Szenarien. - Muss ich das Ergebnis ALLER Promises kennen, unabhĂ€ngig davon, ob sie erfolgreich sind oder fehlschlagen?
Verwenden SiePromise.allSettled(). Dies ist fĂŒr die Handhabung mehrerer unabhĂ€ngiger Aufgaben, bei denen Sie jedes Ergebnis verarbeiten und die Anwendungsresilienz aufrechterhalten möchten. - Interessiert mich nur das allererste Promise, das fertig wird, egal ob es ein Erfolg oder ein Misserfolg ist?
Verwenden SiePromise.race(). Dies ist hauptsĂ€chlich fĂŒr die Implementierung von Timeouts oder anderen Wettlaufsituationen, bei denen nur das erste Ergebnis (jeder Art) zĂ€hlt. - Interessiert mich nur das erste Promise, das ERFOLGREICH ist, und kann ich alle fehlschlagenden ignorieren?
Verwenden SiePromise.any(). Dies ist fĂŒr Szenarien mit Redundanz, wie das Ausprobieren mehrerer Endpunkte fĂŒr dieselbe Ressource.
Fortgeschrittene Muster und Ăberlegungen fĂŒr die Praxis
Obwohl die Promise-Kombinatoren unglaublich leistungsstark sind, erfordert die professionelle Entwicklung oft etwas mehr Nuancen.
Begrenzung der NebenlÀufigkeit und Drosselung
Was passiert, wenn Sie ein Array mit 1.000 IDs haben und fĂŒr jede Daten abrufen möchten? Wenn Sie naiv alle 1.000 Promise-erzeugenden Aufrufe an `Promise.all()` ĂŒbergeben, werden Sie sofort 1.000 Netzwerkanfragen auslösen. Dies kann mehrere negative Konsequenzen haben:
- ServerĂŒberlastung: Sie könnten den Server, von dem Sie anfragen, ĂŒberlasten, was zu Fehlern oder LeistungseinbuĂen fĂŒr alle Benutzer fĂŒhren kann.
- Ratenbegrenzung: Die meisten öffentlichen APIs haben Ratenbegrenzungen. Sie werden wahrscheinlich Ihr Limit erreichen und `429 Too Many Requests`-Fehler erhalten.
- Client-Ressourcen: Der Client (Browser oder Server) könnte Schwierigkeiten haben, so viele offene Netzwerkverbindungen gleichzeitig zu verwalten.
Die Lösung besteht darin, die NebenlĂ€ufigkeit zu begrenzen, indem die Promises in Stapeln (Batches) verarbeitet werden. WĂ€hrend Sie dafĂŒr Ihre eigene Logik schreiben können, handhaben ausgereifte Bibliotheken wie `p-limit` oder `async-pool` dies elegant. Hier ist ein konzeptionelles Beispiel, wie Sie es manuell angehen könnten:
async function processInBatches(items, batchSize, processingFn) {
let results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
console.log(`Processing batch starting at index ${i}...`);
const batchPromises = batch.map(processingFn);
const batchResults = await Promise.allSettled(batchPromises);
results = results.concat(batchResults);
}
return results;
}
// Example usage:
const userIds = Array.from({ length: 20 }, (_, i) => i + 1);
// We will process 20 users in batches of 5
processInBatches(userIds, 5, id => mockApiCall(`user_${id}`, Math.random() * 1000))
.then(allResults => {
console.log('\nBatch processing complete.');
const successful = allResults.filter(r => r.status === 'fulfilled').length;
const failed = allResults.filter(r => r.status === 'rejected').length;
console.log(`Total Results: ${allResults.length}, Successful: ${successful}, Failed: ${failed}`);
});
Ein Hinweis zur Abbrechbarkeit
Eine langjĂ€hrige Herausforderung bei nativen Promises ist, dass sie nicht abbrechbar sind. Sobald Sie ein Promise erstellen, wird es bis zum Abschluss ausgefĂŒhrt. WĂ€hrend `Promise.race` Ihnen helfen kann, ein langsames Ergebnis zu ignorieren, verbraucht die zugrunde liegende Operation weiterhin Ressourcen. FĂŒr Netzwerkanfragen ist die moderne Lösung die `AbortController`-API, mit der Sie einer `fetch`-Anfrage signalisieren können, dass sie abgebrochen werden soll. Die Integration von `AbortController` mit Promise-Kombinatoren kann eine robuste Möglichkeit bieten, lang andauernde nebenlĂ€ufige Aufgaben zu verwalten und aufzurĂ€umen.
Fazit: Vom sequentiellen zum nebenlÀufigen Denken
Asynchrones JavaScript zu meistern, ist eine Reise. Sie beginnt mit dem VerstĂ€ndnis des single-threaded Event-Loops, geht weiter zur Verwendung von Promises und `async/await` fĂŒr mehr Klarheit und gipfelt im nebenlĂ€ufigen Denken zur Maximierung der Leistung. Der Wechsel von einer sequentiellen `await`-Denkweise zu einem parallelorientierten Ansatz ist eine der wirkungsvollsten Ănderungen, die ein Entwickler vornehmen kann, um die ReaktionsfĂ€higkeit von Anwendungen zu verbessern.
Durch die Nutzung der integrierten Promise-Kombinatoren sind Sie gerĂŒstet, um eine Vielzahl von realen Szenarien mit Eleganz und PrĂ€zision zu bewĂ€ltigen:
- Verwenden Sie `Promise.all()` fĂŒr kritische, alles-oder-nichts-DatenabhĂ€ngigkeiten.
- Verlassen Sie sich auf `Promise.allSettled()`, um resiliente UIs mit unabhÀngigen Komponenten zu erstellen.
- Setzen Sie `Promise.race()` ein, um ZeitbeschrÀnkungen durchzusetzen und unbestimmte Wartezeiten zu verhindern.
- WĂ€hlen Sie `Promise.any()`, um schnelle und fehlertolerante Systeme mit redundanten Datenquellen zu schaffen.
Wenn Sie das nĂ€chste Mal mehrere `await`-Anweisungen hintereinander schreiben, halten Sie inne und fragen Sie sich: âSind diese Operationen wirklich voneinander abhĂ€ngig?â Wenn die Antwort nein lautet, haben Sie eine erstklassige Gelegenheit, Ihren Code fĂŒr NebenlĂ€ufigkeit umzugestalten. Beginnen Sie, Ihre Promises gemeinsam zu initiieren, wĂ€hlen Sie den richtigen Kombinator fĂŒr Ihre Logik und beobachten Sie, wie die Leistung Ihrer Anwendung in die Höhe schnellt.