Erstellen einer Versprechenskette rekursiv in JavaScript - Speicherüberlegungen
In diese Antworteine Verheißung, die Kette gebaut ist rekursiv.
Etwas vereinfacht haben wir :
function foo() {
function doo() {
//always return a promise
if (/* more to do */) {
return doSomethingAsync().then(doo);
} else {
return Promise.resolve();
}
}
return doo(); //returns a promise
}
Vermutlich wäre dies Anlass zu einem call-stack und ein Versprechen Kette - sprich "Tiefe" und "Breite".
Ich würde erwarten, dass die Speicher spike größer als entweder die Durchführung einer Rekursion oder Gebäude ein Versprechen Kette allein.
- Ist das so?
- Hat jemand betrachtet, der Speicher Probleme, die der Aufbau einer Kette in dieser Art und Weise?
- Würde Speicherverbrauch unterscheiden zwischen Versprechen libs?
InformationsquelleAutor der Frage Roamer-1888 | 2015-04-28
Du musst angemeldet sein, um einen Kommentar abzugeben.
Eigentlich nicht. Es gibt keine Versprechen Kette hier, wie wir es kennen aus
doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).…
(wasPromise.each
oderPromise.reduce
tun könnten, um nacheinander ausführen-Handler, wenn es wurde so geschrieben).Was wir konfrontiert sind ist hier eine beheben Kette1 - was passiert am Ende, wenn die Basis bei der Rekursion erfüllt ist, ist so etwas wie
Promise.resolve(Promise.resolve(Promise.resolve(…)))
. Dies ist nur "tief", nicht "Breite", wenn Sie es so nennen möchten.Nicht ein spike eigentlich. Sie würde langsam, im Laufe der Zeit bauen Sie einen Großteil der Versprechungen, die aufgelöst werden, mit der innerste, alle vertreten das gleiche Ergebnis. Wenn am Ende Ihre Aufgabe, ist die Bedingung erfüllt und die innerste Versprechen gelöst mit einem tatsächlichen Wert, alle diese Versprechen sollte gelöst werden mit dem gleichen Wert. Das würde am Ende mit
O(n)
Kosten für den Fuß das lösen der Kette (falls implementiert naiv, dies kann auch rekursiv und führen zu einem stack-overflow). Nach, dass all die Versprechungen, außer für Regionen in äußerster Randlage können sich Müll gesammelt.Im Gegensatz dazu, eine Verheißung, die Kette gebaut ist, dass durch so etwas wie
zeigen würde, eine Spitze, die Vergabe von
n
Versprechen Objekte zur gleichen Zeit, und dann langsam lösen Sie eins nach dem anderen, Müll-sammeln der vorherigen, bis nur noch die abgerechnete Ende Versprechen, ist lebendig.Nicht unbedingt. Wie oben schon gesagt, alle die Versprechen, die Massen sind am Ende gelöst, mit dem gleichen Wert2so dass alles, was wir brauchen würden, ist das speichern der äußersten und der innersten Versprechen auf einmal. Alle zwischen-Versprechen kann sich garbage Collection so bald wie möglich, und wir möchten, führen Sie diese Rekursion in Konstanten Raum und Zeit.
In der Tat, diese rekursive Konstruktion ist absolut notwendig für asynchrone Schleifen mit einem dynamischen Zustand (keine Feste Anzahl von Schritten), kann Sie nicht wirklich vermeiden. In Haskell, wo dieser verwendet wird die ganze Zeit für die
IO
Monade, eine Optimierung für Sie umgesetzt wird, nur weil von diesem Fall. Es ist sehr ähnlich zu tail call recursiondie routinemäßig eliminiert, der von Compilern.Ja. Dies war diskutiert Versprechen/aplus zum Beispiel, aber ohne Ergebnis noch.
Viele Versprechen Bibliotheken unterstützen iteration Helfer zu vermeiden, die spike der Verheißung
then
Ketten, wie Bluebird isteach
undmap
Methoden.Meinem eigenen Versprechen, Bibliothek3,4 - Funktion beheben Ketten ohne die Einführung von Speicher-oder Laufzeit-overhead. Wenn ein Versprechen nimmt ein anderer (auch wenn Sie noch angemeldet), Sie werden ununterscheidbar, und der intermediate-Versprechungen werden nicht mehr referenziert, irgendwo.
Ja. Während in diesem Fall können optimiert werden, es selten ist. Insbesondere die ES6-spec erfordert Verspricht, untersuchen Sie den Wert bei jedem
resolve
nennen, so kollabiert die Kette ist nicht möglich. Die zusagen in der Kette könnte auch gelöst werden, aber mit unterschiedlichen Werten (durch die Konstruktion eines Beispiel-Objekt, das Missbräuche Getter, nicht im wirklichen Leben). Das Problem erhoben wurde auf esdiscuss aber bleibt ungelöst.Also, wenn Sie eine undichte Implementierung, sondern müssen asynchrone Rekursion, dann sind Sie besser wieder zurück wechseln, um Rückrufe und verwenden Sie die latente antipattern zu propagieren, die innersten Versprechen führen zu einem einzigen Ergebnis Versprechen.
[1]: keine offizielle Terminologie
[2]: gut, Sie sind entschlossen, miteinander. Aber wir wollen zu beheben, werden Sie mit dem gleichen Wert, wir erwarten, dass
[3]: ohne Papiere Spielplatz, geht aplus. Lesen Sie den code auf deine eigene Gefahr: https://github.com/bergus/F-Promise
[4]: auch implementiert für Creed in dieser pull-request
InformationsquelleAutor der Antwort Bergi
Haftungsausschluss: vorzeitige Optimierung ist schlecht, der wahre Weg, um herauszufinden, über performance-Unterschiede benchmark-codeund du solltest dir keine sorgen machen (ich hatte bis jetzt nur einmal und ich habe zusagen für mindestens 100 Projekte).
Jadie Versprechungen würden zu "erinnern", was Sie sind, wenn Sie dies tun, für 10000 verspricht, Sie würden eine 10000 langen Versprechen Kette, wenn Sie das nicht tun, dann werden Sie nicht (z.B. bei Rekursion) - dies gilt für jede queueing-flow-control.
Wenn Sie haben zu verfolgen, 10000 zusätzliche Dinge (die Operationen), dann müssen Sie halten die Erinnerung für Sie und das braucht Zeit, wenn diese Zahl eine million ist es vielleicht nicht lebensfähig sein. Diese variiert zwischen Bibliotheken.
Natürlich ist das ein großes Problem, und ein Anwendungsfall für die Verwendung von so etwas wie
Promise.each
in Bibliotheken, wie bluebird überthen
in der Lage, die Verkettung.Ich persönlich habe in meinem code zu vermeiden, ist dieser Stil für eine schnelle app, die durchläuft alle Dateien in einer VM mal - aber in den allermeisten Fällen ist es ein nicht-Thema.
Ja, sehr. Zum Beispiel bluebird 3.0 wird nicht weisen Sie eine extra Warteschlange, wenn er erkennt, Versprechen die Bedienung ist schon die asynchrone (zum Beispiel, wenn es beginnt mit einem Versprechen.Verzögerung) und wird nur auszuführen, was synchron (da das async garantiert bereits erhalten).
Dies bedeutet, dass das, was ich behauptete in meiner Antwort auf die erste Frage ist nicht immer wahr (aber wahr ist in den regelmäßigen Gebrauch-Fall) 🙂 Native verspricht, wird nie in der Lage sein, dies zu tun, es sei denn, die interne Unterstützung ist zur Verfügung gestellt.
Dann wieder - es ist keine überraschung, da Versprechen Bibliotheken unterscheiden sich um Größenordnungen voneinander.
InformationsquelleAutor der Antwort Benjamin Gruenbaum
Ich kam gerade aus einem hack, die helfen können die Lösung des Problems: nicht die Rekursion in den letzten
then
eher, tun Sie es in den letztencatch
dacatch
ist aus dem lösen der Kette. Mit deinem Beispiel wäre es so:InformationsquelleAutor der Antwort dotslashlu
Zur Ergänzung der awesome vorhandene Antworten, die ich gerne zur Veranschaulichung der Ausdruck, der das Ergebnis eines asynchronen Rekursion. Der Einfachheit halber verwende ich eine einfache Funktion berechnet, dass die macht von einer bestimmten Basis und exponent. Die rekursive und base-case-entspricht den OP ' s Beispiel:
Mit der Hilfe von einigen der substitution-Schritte des rekursiven Teil ersetzt werden kann. Bitte beachten Sie, dass dieser Ausdruck ausgewertet werden kann, in Ihrem browser:
Interpretation:
new Promise(res => setTimeout(res, 0, 8))
der Testamentsvollstrecker ist aufgerufen, sofort und führt eine nicht-bllocking Berechnung (nachgestellt mitsetTimeout
). Dann ein unruhigesPromise
zurückgegeben. Dies ist gleichbedeutend mitdoSomethingAsync()
des OP ' s Beispiel.Promise
über.then(...
. Hinweis: Der Körper dieser Rückruf wurde nun mit dem Körperpowerp
.then
- handler-Struktur aufbauen, bis der basisfall der Rekursion ist erreicht. Die base-case-gibt einePromise
gelöst mit1
.then
- handler-Struktur ist "abgewickelt" durch aufrufen der zugehörigen callback entsprechend.Warum ist die erzeugte Struktur und nicht angekettet? Weil der rekursive Fall innerhalb der
then
- Handler verhindert, dass Sie wieder einen Wert, bis der basisfall erreicht ist.Wie kann das funktionieren, ohne ein stack? Die damit verbundenen Rückrufe bilden eine "Kette", die Brücken, die aufeinander microtasks der Haupt-event-Schleife.
InformationsquelleAutor der Antwort ftor
Diese Verheißung Muster generieren, die eine rekursive Kette. Also, jede resolve() erzeugt einen neuen stack-frame (mit eigenen Daten), die Verwendung von Speicher. Dies bedeutet, dass die große Anzahl von verketteten Funktionen, die mit diesem Versprechen Muster erzeugen von stack-overflow-Fehler.
Um dies zu veranschaulichen, werde ich mit einem winzigen Versprechen-Bibliothek namens Folgedie ich geschrieben habe. Es basiert auf Rekursion zu erreichen sequenzielle Ausführung für verkettete Funktionen:
Sequenz funktioniert sehr gut für kleine/mittlere Ketten, im Bereich von 0-500 Funktionen. Aber bei über 600 Ketten-Sequenz beginnt degradating und erzeugen oft stack-überlauf-Fehler.
In der unteren Zeile ist: derzeitRekursion-basierte Versprechen Bibliotheken sind besser geeignet für kleinere/mittlere Funktion Ketten, während verringern-basierten Versprechen Implementierungen sind ok, für alle Fälle, auch größere Ketten.
Dies bedeutet natürlich nicht, dass Rekursion-basierte Versprechen sind schlecht. Wir müssen Sie nur nutzen Ihnen mit Ihren Einschränkungen im Hinterkopf. Auch ist es selten, dass Sie brauchen, um Kette, die viele Anrufe (>=500) über verspricht. Ich in der Regel finden mich mit Ihnen für eine async-Konfigurationen nutzen, die stark auf ajax. Aber auch wenn die meisten komplexen Fällen, die ich noch nicht gesehen, eine situation mit mehr als 15 Ketten.
On a side note...
Diese Statistiken wurden abgerufen von tests mit anderen von meinen Bibliotheken - provisnr - erfasst die erreichte Anzahl der Funktionsaufrufe innerhalb einer bestimmten Zeitspanne.
InformationsquelleAutor der Antwort neatsu