2014-05-03 16 views
8

den folgenden JavaScript-Code unter BerücksichtigungWarum verwenden Promise-Bibliotheken Ereignisschleifen?

var promise = new Promise(); 
setTimeout(function() { 
    promise.resolve(); 
}, 10); 

function foo() { } 
promise.then(foo); 

In den Versprechen Implementierungen ich gesehen habe, promise.resolve() würde einfach eine Eigenschaft, das Versprechen, um anzuzeigen, wurde aufgelöst und foo() würde später während eines heißen Event-Schleife, aber es scheint, als ob die provide.resolve() genug Informationen hätte, um alle zurückgestellten Funktionen wie foo() sofort aufzurufen.

Die Event-Loop-Methode scheint die Komplexität zu erhöhen und die Leistung zu reduzieren. Warum wird sie verwendet? Die meisten meiner Versprechen werden mit JavaScript ausgeführt, aber der Grund für meine Frage liegt in der Umsetzung von Versprechen in sehr leistungsintensiven Fällen wie C++ - Spielen. In diesem Fall frage ich mich, ob ich einige der Vorteile nutzen könnte von Versprechen ohne den Overhead einer Ereignisschleife.

+1

Wenn Sie möchten, dass ein Versprechen sofort aufgelöst wird, rufen Sie einfach 'promise.resolve()' auf und es ruft zu diesem Zeitpunkt alle registrierten Lösungshandler auf. Wenn Sie möchten, dass sich Ihre eigene Ereignisschleife auflöst, bevor das Versprechen aufgelöst wird, können Sie 'setTimeout (function() {provive.resolve()}, 10);'. Aber es hängt davon ab, wie das '.resolve()' sich verhalten soll. Es ist nicht erforderlich, 'setTimeout()' zu verwenden. – jfriend00

+1

Ja, das verstehe ich. Was ich verstehen möchte ist, wenn setTimeout verwendet wird, wird foo() nicht sofort nach 10 Millisekunden aufgerufen, sondern die Auflösung wird in eine Queue gestellt und foo() wird später ausgeführt. – silentorb

+0

Welches Versprechen verspricht die Umsetzung? Da es viele verschiedene Implementierungen gibt, ist es schwierig, eine solche Frage zu stellen, ohne sich auf eine bestimmte Implementierung zu beziehen. Soweit ich weiß, gibt es keine Spezifikation, die besagt, dass Rückrufe erst nach einer Verzögerung aufgerufen werden sollten. – jfriend00

Antwort

9

Alle Versprechen Implementierungen, zumindest gute tun, dass.

Dies liegt daran, dass das Mischen von Synchronität in eine asynchrone API releasing Zalgo ist.

Die Tatsache Versprechen nicht sofort manchmal auflösen und verzögern bedeutet manchmal, dass die API konsistent ist. Andernfalls erhalten Sie undefiniertes Verhalten in der Reihenfolge der Ausführung.

function getFromCache(){ 
     return Promise.resolve(cachedValue || getFromWebAndCache()); 
} 

getFromCache().then(function(x){ 
    alert("World"); 
}); 
alert("Hello"); 

Die Tatsache, Versprechen Bibliotheken verschieben bedeutet, dass die Reihenfolge der Ausführung des obigen Block gewährleistet ist. In gebrochenen Versprechen Implementierungen wie jQuery ändert sich die Reihenfolge abhängig davon, ob das Element aus dem Cache abgerufen wird oder nicht. Das ist gefährlich.

Die nichtdeterministische Ausführungsreihenfolge ist sehr riskant und eine häufige Fehlerquelle. Die Promises/A + -Spezifikation wirft Sie hier in die Erfolgsspur.

+1

Für das, was es wert ist, hier ist eine Versprechen Implementierung für Objective Ich sehe vor kurzem und sieht gut aus https://github.com/mxcl/PromiseKit es ist Promises/A + Beschwerde –

+1

PromiseKit ist überhaupt nicht Promise/A + konform. Es hat keine öffentliche Resolver-API, stattdessen muss man die bereitgestellten Kategorien verwenden, die den Resolver-Aspekt für bestimmte Foundation-Klassen (z. B. NSURLConnection) implementieren (oder implementieren müssen). Wenn eine Zusage durch gegebene Kategorien aufgelöst wird, wird die Fortsetzung außerdem * synchron * aufgerufen, was den Handler im aktuellen Ausführungskontext ausführt, der der Aufrufstelle möglicherweise nicht bekannt ist. Tatsächlich wird PromiseKit tatsächlich die "kaputte Implementierung" sein, von der Sie sprechen (und ich stimme zu;)) – CouchDeveloper

+2

PromiseKit veröffentlicht Zalgo nicht mehr. Gib ihm eine Pause, ich habe es vor 4 Wochen veröffentlicht. – mxcl

2

Versprechen sind alles über cooperative multitasking.

So ziemlich die einzige Methode, um das zu erreichen, ist die Verwendung der Nachrichten-basierten Planung.

Timer (in der Regel mit 0 Verzögerung) werden einfach verwendet, um die Aufgabe/Nachricht in die Nachrichtenwarteschlange zu schreiben - yield-to-next-task-in-the-queue. So arbeitet die ganze Formation, bestehend aus kleinen Event-Handlern, und häufiger Sie - reibungsloser all diese Arbeit.

+0

@BenjaminGruenbaum Resolve und Reject Methoden des Versprechens sind asynchron von sich aus. Der Anrufer darf nicht erwarten, dass er für einen erheblichen Zeitraum blockiert wird, wenn er anruft. Die vorgeschlagene sofortige Ausführung kann den Anrufer blockieren. –

+0

@BenjaminGruenbaum, wie ich schon sagte, Anrufer darf nicht blockiert werden durch die Ausführung von Auflösungs-/Ablehnungsoperationen. Jeder Task erhält seinen eigenen Zeitrahmen: Der Aufrufer-Code behandelt seine eigenen Sachen und der Erfüllungs-Code läuft in einem separaten Ausführungsrahmen ab (Task, Timer-Event-Handler hier). Sonst wäre es ziemlich schwierig, vernünftiges Multitasking zu erreichen. –

6

Ob promise.resolve() synchron oder asynchron seine Fortsetzungen ausführt, hängt wirklich von der Implementierung ab.

Darüber hinaus ist die "Ereignisschleife" nicht der einzige Mechanismus, der einen anderen "Ausführungskontext" bereitstellt. Es kann andere Mittel geben, zum Beispiel Threads oder Thread-Pools, oder man denke an GCD (Grand Central Dispatch, Versand-Lib), die Versandwarteschlangen bereitstellt.

The Promises/A+ Spec erfordert deutlich, daß die Fortsetzung (die jeweils den onFulfilledonRejected handler) sein wird asynchron in Bezug auf die „Ausführungskontext“ ausgeführt, in dem die then Methode aufgerufen wird.

  1. onFulfilled oder onRejected darf nicht bis der Stapel enthält Code nur Plattform Ausführungskontext aufgerufen werden. [3.1].

Unter den Notizen können Sie lesen, was das eigentlich bedeutet:

hier „Plattform Code“ Motor, Umwelt und Implementierungscode Versprechen. In der Praxis stellt diese Anforderung sicher, dass onFulfilled und onRejected asynchron ausgeführt werden, nach der Ereignisschleife, die dann aufgerufen wird, und mit einem neuen Stack.

Hier wird jedes Ereignis in einem anderen "Ausführungskontext" ausgeführt, obwohl es sich um die gleiche Ereignisschleife und den gleichen "Thread" handelt.

Da die Promises/A + Spezifikation für die Javascript-Umgebung geschrieben ist, eine allgemeinere Beschreibung würde einfach verlangen, dass die Fortsetzungasynchron ausgeführt mit Bezug auf den Anrufer Aufruf die then Methode sein wird.

Dafür gibt es gute Gründe!

Beispiel (Pseudocode):

promise = async_task(); 
printf("a"); 
promise.then((int result){ 
    printf("b"); 
}); 
printf("c"); 

Unter der Annahme, der Handler (Fortsetzung) auf dem gleiche Gewinde wie die Call-Site ausgeführt wird, soll die Reihenfolge der Ausführung sein, dass die Konsole dies zeigt:

acb 

Insbesondere wenn ein Versprechen bereits gelöst wird, neigen einige Implementierungen die Fortsetzung „sofort“ aufzurufen (das heißt synchron) auf dem gleichen Ausführungskontext. Dies würde eindeutig gegen die oben genannte Regel verstoßen.

Der Grund für die Regel die Fortsetzung aufzurufen immer asynchron ist, dass ein Anruf Ort eine Garantie über die relative Reihenfolge der Ausführung der Handler und Code die then und über die Fortführung Anweisung nach in jedem Szenario haben muss. Das heißt, unabhängig davon, ob ein Versprechen bereits gelöst ist oder nicht, muss die Reihenfolge der Ausführung der Anweisungen gleich sein. Andernfalls funktionieren komplexere asynchrone Systeme möglicherweise nicht zuverlässig.

Ein weiteres schlechtes Design Wahl für Implementierungen in anderen Sprachen, die mehrere simultane Ausführungskontexte haben - sagen wir eine Multi-Threaded-Umgebung (irrelevant in JavaScript, da nur ein Thread der Ausführung ist) ist, dass die Fortsetzung synchron aufgerufen wird in Bezug auf die resolve Funktion. Dies ist sogar problematisch, wenn die asynchrone Task in einem späteren Ereignisschleifenzyklus endet und somit die Fortsetzung asynchron in Bezug auf die Call-Site ausgeführt wird.

Wenn jedoch die resolve Funktion wird durch die asynchrone Aufgabe aufgerufen werden, wenn es fertig ist, kann diese Aufgabe auf einen privaten Ausführungskontext auszuführen (sagt den „Worker-Thread“).Dieser "Worker-Thread" ist normalerweise ein dedizierter und möglicherweise speziell konfigurierter Ausführungskontext - der dann resolve aufruft. Wenn die resolve Funktion synchron die Fortsetzung ausführen wird, wird die Fortsetzung auf dem privaten Ausführungskontext der Aufgabe ausgeführt - was im Allgemeinen nicht gewünscht ist.