Ich habe einen Code, der über eine Liste iteriert, die aus einer Datenbank abgefragt wurde, und eine HTTP-Anforderung für jedes Element in dieser Liste stellt. Diese Liste kann manchmal eine relativ große Zahl sein (zu Tausenden), und ich möchte sicherstellen, dass ich keinen Webserver mit Tausenden von gleichzeitigen HTTP-Anforderungen erreiche.
Eine abgekürzte Version dieses Codes sieht derzeit ungefähr so aus ...
function getCounts() {
return users.map(user => {
return new Promise(resolve => {
remoteServer.getCount(user) // makes an HTTP request
.then(() => {
/* snip */
resolve();
});
});
});
}
Promise.all(getCounts()).then(() => { /* snip */});
Dieser Code wird auf Knoten 4.3.2 ausgeführt. Kann, Promise.all
um es noch einmal zu wiederholen, so verwaltet werden, dass zu einem bestimmten Zeitpunkt nur eine bestimmte Anzahl von Versprechungen ausgeführt wird?
javascript
node.js
es6-promise
Chris
quelle
quelle
Promise.all
dies den Fortschritt der Versprechen schafft - die Versprechen tun dies selbst,Promise.all
warten nur auf sie.Promise
Konstruktor-Antimuster !Antworten:
Beachten Sie, dass
Promise.all()
dies nicht die Versprechen auslöst, ihre Arbeit zu beginnen , sondern das Versprechen selbst erstellt.Vor diesem Hintergrund besteht eine Lösung darin, zu prüfen, wann immer ein Versprechen gelöst wird, ob ein neues Versprechen gestartet werden soll oder ob Sie bereits am Limit sind.
Es ist jedoch wirklich nicht nötig, das Rad hier neu zu erfinden. Eine Bibliothek, die Sie für diesen Zweck verwenden könnten, ist
es6-promise-pool
. Aus ihren Beispielen:quelle
P-Limit
Ich habe die Begrenzung der Versprechen-Parallelität mit einem benutzerdefinierten Skript, Bluebird, es6-Versprechen-Pool und p-Limit verglichen. Ich glaube, dass p-limit die einfachste, abgespeckte Implementierung für diesen Bedarf hat. Siehe ihre Dokumentation .
Bedarf
Zum Beispiel mit Async kompatibel sein
Mein Beispiel
In diesem Beispiel müssen wir für jede URL im Array eine Funktion ausführen (z. B. eine API-Anforderung). Hier heißt das
fetchData()
. Wenn wir eine Reihe von Tausenden von Elementen verarbeiten müssten, wäre Parallelität definitiv nützlich, um CPU- und Speicherressourcen zu sparen.Das Konsolenprotokollergebnis ist ein Array Ihrer Antwortdaten für aufgelöste Versprechen.
quelle
Wenn Sie wissen, wie Iteratoren funktionieren und wie sie verwendet werden, benötigen Sie keine zusätzliche Bibliothek, da es sehr einfach werden kann, Ihre eigene Parallelität selbst zu erstellen. Lassen Sie mich demonstrieren:
Wir können denselben Iterator verwenden und ihn für alle Mitarbeiter freigeben.
Wenn Sie
.entries()
stattdessen verwendet.values()
hätten, hätten Sie ein 2D-Array erstellt, mit[[index, value]]
dem ich unten mit einer Parallelität von 2 demonstrieren werdeDies hat den Vorteil, dass Sie eine Generatorfunktion haben können, anstatt alles auf einmal bereit zu haben.
Hinweis: Der Unterschied zum Beispiel- Async-Pool besteht darin, dass zwei Worker erzeugt werden. Wenn also ein Worker aus irgendeinem Grund einen Fehler bei Index 5 ausgibt, wird der andere Worker nicht davon abgehalten, den Rest zu erledigen. Sie gehen also von 2 Parallelität auf 1 zurück (damit es hier nicht aufhört). Mein Rat ist also, dass Sie alle Fehler innerhalb der
doWork
Funktion abfangenquelle
Verwenden von
Array.prototype.splice
quelle
arr.splice(-100)
wenn die Reihenfolge nicht stimmt. Vielleicht können Sie das Array umkehren: PDie Promise.map von bluebird kann eine Parallelitätsoption verwenden, um zu steuern, wie viele Versprechen parallel ausgeführt werden sollen. Manchmal ist es einfacher, als
.all
weil Sie das Versprechen-Array nicht erstellen müssen.quelle
Verwenden Sie anstelle von Versprechungen zur Begrenzung von http-Anforderungen die integrierten http.Agent.maxSockets des Knotens . Dies beseitigt die Notwendigkeit, eine Bibliothek zu verwenden oder Ihren eigenen Pooling-Code zu schreiben, und bietet den zusätzlichen Vorteil, dass Sie mehr Kontrolle darüber haben, was Sie einschränken.
Beispielsweise:
Wenn Sie mehrere Anforderungen an denselben Ursprung stellen, kann es auch von Vorteil sein, den Wert
keepAlive
auf true zu setzen (weitere Informationen finden Sie in den obigen Dokumenten).quelle
Ich schlage den Async-Pool der Bibliothek vor: https://github.com/rxaviers/async-pool
Beschreibung:
Verwendung:
quelle
Hier ein grundlegendes Beispiel für Streaming und 'p-Limit'. Es überträgt http read stream an mongo db.
quelle
Es kann durch Rekursion aufgelöst werden.
Die Idee ist, dass Sie anfangs die maximal zulässige Anzahl von Anforderungen senden und jede dieser Anforderungen sich nach Abschluss rekursiv selbst weiter senden sollte.
quelle
Hier ist meine ES7-Lösung für eine Copy-Paste-freundliche und vollständige
Promise.all()
/map()
alternative Funktion mit einem Parallelitätslimit.Ähnlich
Promise.all()
wird die Rückgabereihenfolge sowie ein Fallback für nicht versprochene Rückgabewerte beibehalten.Ich habe auch einen Vergleich der verschiedenen Implementierungen beigefügt, da er einige Aspekte veranschaulicht, die einige der anderen Lösungen übersehen haben.
Verwendung
Implementierung
Vergleich
Fazit
asyncPool()
sollte die beste Lösung sein, da neue Anforderungen gestartet werden können, sobald eine vorherige abgeschlossen ist.asyncBatch()
wird als Vergleich aufgenommen, da die Implementierung einfacher zu verstehen ist, die Leistung jedoch langsamer sein sollte, da alle Anforderungen im selben Stapel abgeschlossen sein müssen, um den nächsten Stapel zu starten.In diesem erfundenen Beispiel ist die nicht begrenzte Vanille
Promise.all()
natürlich die schnellste, während die anderen in einem realen Überlastungsszenario eine wünschenswertere Leistung erbringen könnten.Aktualisieren
Die Async-Pool-Bibliothek, die andere bereits vorgeschlagen haben, ist wahrscheinlich eine bessere Alternative zu meiner Implementierung, da sie fast identisch funktioniert und eine präzisere Implementierung mit einer cleveren Verwendung von Promise.race () aufweist: https://github.com/rxaviers/ async-pool / blob / master / lib / es7.js
Hoffentlich kann meine Antwort immer noch einen pädagogischen Wert haben.
quelle
Also habe ich versucht, einige Beispiele für meinen Code zum Laufen zu bringen, aber da dies nur für ein Importskript und nicht für Produktionscode war, war die Verwendung der Stapelversprechen des npm-Pakets sicherlich der einfachste Weg für mich
HINWEIS: Benötigt Laufzeit, um Promise zu unterstützen oder um Polyfilled zu sein.
Api batchPromises (int: batchSize, array: Collection, i => Promise: Iteratee) Das Promise: Iteratee wird nach jedem Batch aufgerufen.
Verwenden:
quelle
Rekursion ist die Antwort, wenn Sie keine externen Bibliotheken verwenden möchten
quelle
Dies habe ich
Promise.race
in meinem Code hier verwendetWenn Sie ein Beispiel sehen möchten : https://jsfiddle.net/thecodermarcelo/av2tp83o/5/
quelle
Promise.race
Wann immer möglich, versuche ich, solche Dinge selbst zu entwickeln, anstatt in eine Bibliothek zu gehen. Am Ende lernst du viele Konzepte, die vorher entmutigend wirkten.
Was haltet ihr von diesem Versuch:
(Ich habe viel darüber nachgedacht und ich denke, es funktioniert, aber weist darauf hin, ob es nicht so ist oder ob etwas grundlegend falsch ist.)
Dieser Ansatz bietet eine nette API, ähnlich wie Thread-Pools in Scala / Java.
Nach dem Erstellen einer Instanz des Pools mit
const cappedPool = new Pool(2)
, geben Sie ihm einfach VersprechencappedPool.add(() => myPromise)
.Ohne es zu merken, müssen wir sicherstellen, dass das Versprechen nicht sofort beginnt, und deshalb müssen wir es mit Hilfe der Funktion "träge" bereitstellen.
Beachten Sie vor allem, dass das Ergebnis der Methode
add
ein Versprechen ist, das mit dem Wert Ihres ursprünglichen Versprechens abgeschlossen / aufgelöst wird ! Dies ermöglicht eine sehr intuitive Bedienung.quelle
Leider gibt es keine Möglichkeit, dies mit nativem Promise.all zu tun. Sie müssen also kreativ sein.
Dies ist der schnellste und prägnanteste Weg, den ich finden konnte, ohne externe Bibliotheken zu verwenden.
Es verwendet eine neuere Javascript-Funktion, die als Iterator bezeichnet wird. Der Iterator verfolgt grundsätzlich, welche Elemente verarbeitet wurden und welche nicht.
Um es im Code zu verwenden, erstellen Sie ein Array von asynchronen Funktionen. Jede asynchrone Funktion fragt denselben Iterator nach dem nächsten Element, das verarbeitet werden muss. Jede Funktion verarbeitet ihr eigenes Element asynchron und fordert den Iterator nach Abschluss eines neuen Elements auf. Sobald dem Iterator die Elemente ausgehen, sind alle Funktionen abgeschlossen.
Vielen Dank an @Endless für die Inspiration.
quelle