.MB: Promises & Callbacks

moin,

was ist der Unterschied zwischen callbacks und promisses? Wie setze ich es ein, wenn es sinnvoll ist?

Vorgeschichte: Ich will einen Verbindung und Handling für eine Applikation in Nodejs TypeScript basteln. Wenn ich über MongoDB Collections Daten auslese klappt's nicht. Ich vermute mal das der Zugriff auf ein anderes Programm welches Daten für das aufrufende Skript bereithält und sie übergibt, soviel Zeit in Anspruch nimmt, sodass das Script womit ich dieses Callback aufgerufen habe, einfach weiter läuft ohne eine Antwort zu erwarten. Indem Fall wird n leeres Array als return wert übergeben weil ja der Datenaustausch zwischen MongoDBund NodeJS noch nicht Fertig ist.

lieben Gruß

MB

  1. Tach!

    was ist der Unterschied zwischen callbacks und promisses?

    Ein Callback ist eine Funktion(sreferenz), die man übergibt und die aufgerufen werden soll. Ein Callback kann zum Beispiel die Vergleichsfunktion sein, die man Array.sort() oder .find() etc. übergeben kann. Da tut ein Callback Teile der zu erledigenden Aufgabe.

    Auch Promises verwenden Callbacks. Promises sind ein Programmiermuster, das für asynchrone Abläufe entwickelt wurde. Die Callbacks werden jedoch erst aufgerufen, wenn die Aufgabe erledigt oder mit Fehler abgebrochen wurde.

    Während nach einem Array.sort() oder .find() die Verarbeitung synchron in der nächsten Zeile weiterläuft, muss man bei den Promises die darauf aufbauende Arbeit innerhalb der Callbacks stattfinden lassen.

    Wie setze ich es ein, wenn es sinnvoll ist?

    So wie es dokumentiert ist.

    Vorgeschichte: Ich will einen Verbindung und Handling für eine Applikation in Nodejs TypeScript basteln. Wenn ich über MongoDB Collections Daten auslese klappt's nicht. Ich vermute mal das der Zugriff auf ein anderes Programm welches Daten für das aufrufende Skript bereithält und sie übergibt, soviel Zeit in Anspruch nimmt, sodass das Script womit ich dieses Callback aufgerufen habe, einfach weiter läuft ohne eine Antwort zu erwarten.

    Kenn ich nicht, aber wenn die Abfragefunktion asynchron abläuft, kommst du nicht umhin, dich im entsprechenden "Erledigt"-Callback einzunisten, um Folgeaufgaben zu erledigen. Das ist nicht anders als beispielsweise bei Ajax-Aufrufen. Nach dem .send() ist noch kein Ergebnis da, das gibts erst in der in onreadystatechange übergebenen Callback-Funktion.

    dedlfix.

    1. Hello Dedlfix,

      Während nach einem Array.sort() oder .find() die Verarbeitung synchron in der nächsten Zeile weiterläuft, muss man bei den Promises die darauf aufbauende Arbeit innerhalb der Callbacks stattfinden lassen.

      Könntest Du den Satz bitte nochmal für Doofe erklären bzw. ein leicht verständliches Beispiel geben?
      Da komme ich nicht mit.

      Liebe Grüße
      Tom S.

      --
      Die Krawatte ist das Kopftuch des Westens
      1. Tach!

        Während nach einem Array.sort() oder .find() die Verarbeitung synchron in der nächsten Zeile weiterläuft, muss man bei den Promises die darauf aufbauende Arbeit innerhalb der Callbacks stattfinden lassen.

        Könntest Du den Satz bitte nochmal für Doofe erklären bzw. ein leicht verständliches Beispiel geben?

        Nein, tut mir leid, aber eine gewisse Grundintelligenz muss schon vorhanden sein.

        Da komme ich nicht mit.

        An welcher Stelle genau? Ist dir bereits die Arbeitsweise von Callbacks in Array.sort() und .find() unklar, oder kennst du das Konzept asynchroner Programmierung nicht, oder sind dir lediglich Promises unbekannt? Für letzteres gibt es Tutorials, zum Beispiel jenes.

        dedlfix.

        1. Hallo

          ... Für letzteres (Promises) gibt es Tutorials, zum Beispiel jenes.

          in in diesem Tutorial findet mal auch noch den Link zu „JavaScript Promises: an Introduction“.

          Gruß
          Jürgen

          1. Hello,

            ... Für letzteres (Promises) gibt es Tutorials, zum Beispiel jenes.

            in in diesem Tutorial findet mal auch noch den Link zu „JavaScript Promises: an Introduction“.

            Und in der Wikipedia findet man noch eine allgemeine Erklärung zu Promises/Futures

            Wie ist das denn dann in JavaScript, wenn ich auf Ergebnis (Future) zugreife? Wird da die Ausführung zugreifenden Programms dann auch solange blockiert, bis es zur Verfügung steht?

            Liebe Grüße
            Tom S.

            --
            Die Krawatte ist das Kopftuch des Westens
            1. Tach!

              Wie ist das denn dann in JavaScript, wenn ich auf Ergebnis (Future) zugreife? Wird da die Ausführung zugreifenden Programms dann auch solange blockiert, bis es zur Verfügung steht?

              Üblicherweise fängt solch eine Verarbeitungskette mit einem asynchronen Request zum Server an. Threads gibt es ja nicht und Promises sind auch kein Ersatz für Threads. Eine weitere Möglichkeit sind, vermute ich, Web Worker, aber die habe ich mir noch nicht angeschaut. Bleiben wir bei XMLHttpRequest (vulgo Ajax). Du startest eine Anforderung und dein dort nachfolgender Code wird ausgeführt. Da blockiert erstmal nichts.

              Wenn dein Callback aufgerufen wird, ist ohne Threads natürlich auch keine andere Nebenläufigkeit möglich. Der Callback, beziehungsweise der Handler, der die Antwort vom XMLHttpRequest verarbeitet und dann deinen Callback aufruft, kann auch erst dann ausgeführt werden, wenn nichts anderes läuft. Eine lange Aufgabe, die die Steuerung nicht wieder an die Event Loop zurückgibt, blockiert das System.

              dedlfix.

              1. Tach!

                Üblicherweise fängt solch eine Verarbeitungskette mit einem asynchronen Request zum Server an.

                Da fällt mir doch ein, dass ich mal ein Promise für einen Dialog verwendet haben. Aufgabenstellung war, dass in einem Dialog eine Eingabe erfolgen soll und der mit OK oder Cancel abgebrochen werden konnte. Im OK-Fall sollte eine Folgeverarbeitung starten. Wie macht man das ohne Promise? Man hängt sich in das Klick-Event vom OK-Button rein. Oder in andere Events, die die verwendete Modal-Dialog-Komponente liefert. Das sieht programmiertechnisch unhübsch aus, wenn man an einer Stelle den Dialog startet und irgendwo anders die Folgeverarbeitung stattfindet. "Irgendwo anders" ist hier definiert als Callback, den man vor dem Aufruf zugewiesen hat.

                var dialog = new Dialog()
                dialog.accept = function() { ... }
                dialog.cancel = function() { ... }
                dialog.open();
                

                Irgendwie so sieht das aus. Man liest den Code und sieht accept und cancel als Vorbereitung und kommt dann zum open() und zur Fortsetzung geht man wieder nach open zu accept oder cancel. Hier im Beispiel sieht das noch kompakt und verständlich aus, in echtem Code liegen da viele Zeilen dazwischen.

                Mit Promise sieht der Code so aus.

                var dialog = new Dialog()
                dialog.open()
                  .then(function () { ... })
                  .catch(function () { ... })
                  .finally(function () { dialog.close() });
                

                Das liest sich flüssiger und von oben nach unten in der Reihenfolge des Passierens. Erst das Öffnen und anschließend steht, was anschließend passieren soll.

                dedlfix.

                1. War das ein handgemachtes Promise oder eins aus einer JS Library? Denn finally ist nicht in der Spec.

                  Rolf

                  1. Tach!

                    War das ein handgemachtes Promise oder eins aus einer JS Library? Denn finally ist nicht in der Spec.

                    Das standardisierte Promise ist ja nicht die erste oder einzige Implementation von Promises in Javascript. Schade, dass da kein finally oder vergleichbares implementiert wurde. Ohne das muss man den in jedem Fall auszuführenden Code doppelt in then und catch notieren.

                    Noch einen großen Schritt weiter als Promises geht übrigens ReactiveX/RxJS.

                    dedlfix.

                    1. Kann man das finally nicht durch ein weiteres .then und geschickte Rückgaben aus den ersten beiden Handlern abbilden?

                      getPromise()                                  // liefert P1
                      .then(function(promisedValue) {                  // then-Handler
                         // process value, with possible exception  
                         return true;
                      })                                            // liefert P2
                      .catch(function(failReason) {                    // catch-Handler
                         // handle failure
                         return false;
                      })                                            // liefert P3
                      .then(function(thenOrCatch) {                    // finally-Handler
                         // do common stuff
                      });
                      

                      Fall 1:
                      Das Promise P1 wird erfüllt und liefert den promisedValue. Dieser wird an den then-Handler übergeben und verarbeitet.

                      Fall 1a: Die Verarbeitung gelingt und es wird keine Exception geworfen. Dann wird der then-Handler mit true verlassen und P2 wird erfüllt. Da für P2 kein then-Handler vorgesehen ist, erfüllt sich P3 automatisch auch und der finally-Handler wird mit thenOrCatch=true aufgerufen

                      Fall 1b: Die Verarbeitung gelingt nicht, eine Exception wird geworfen. Dadurch wird P2 gebrochen und der catch-Handler wird aufgerufen. Die Exception bekommt er als Parameter. Der Catch-Handler liefert false zurück, wodurch P3 sich erfüllt und finally-Handler läuft mit thenOrCatch=false an

                      Fall 2: Das Promise P1 wird gebrochen. Da P1.then keinen Catch-Handler hat, bricht P2 automatisch, der catch-Handler für P2 läuft an, gibt false zurück und erfüllt damit P3. Auch jetzt läuft der finally-Handler an.

                      Rolf

                      1. Tach!

                        Kann man das finally nicht durch ein weiteres .then und geschickte Rückgaben aus den ersten beiden Handlern abbilden?

                        Könnte gehen. after a catch the chain is restored (im ersten Beispiel). Ist jetzt aber semantisch nicht besonders schön.

                        dedlfix.

            2. Hallo Tom,

              in „erster Näherung“ sind Promises nur eine elegante Methode, um asynchrone Prozesse und deren Callbacks zu organisieren. Neu ist (aus meiner Sicht) die Möglichkeit, mit .all auf den Abschluss vieler Ereignisse zu warten.

              Gruß
              Jürgen

              1. Hello,

                in „erster Näherung“ sind Promises nur eine elegante Methode, um asynchrone Prozesse und deren Callbacks zu organisieren. Neu ist (aus meiner Sicht) die Möglichkeit, mit .all auf den Abschluss vieler Ereignisse zu warten.

                Ich denke da noch einen anderen Weg...

                Angenommen, irgendeine Collection wird per Xhttp-Request auf einer Webseite angefordert, dann müsste es möglich sein, das Promise in der Session zur Seite zwischenzuspeichern und das Ergebnis in einem ganz anderen Dokument abzuholen.

                Was macht denn die Ressource (das Script dazu), die mittels Xhttp-Request erzeugt wurde solange, bis sie abgefordert wird?

                Ob die Kerne der Prozessoren, die man mittles "Promises" zum Arbeiten versklavt hat nun in einer Maschine wohnen oder in getrennten müsste doch im Prinzip egal sein, oder?

                Liebe Grüße
                Tom S.

                --
                Die Krawatte ist das Kopftuch des Westens
                1. Tach!

                  Angenommen, irgendeine Collection wird per Xhttp-Request auf einer Webseite angefordert, dann müsste es möglich sein, das Promise in der Session zur Seite zwischenzuspeichern und das Ergebnis in einem ganz anderen Dokument abzuholen.

                  Theoretisch ja, aber der asynchrone Aufruf funktioniert ja nicht zwischen zwei Dokumenten. Du könntest dann nur ein bereits erledigtes Promise speichern (egal ob resolved oder rejected).

                  Was macht denn die Ressource (das Script dazu), die mittels Xhttp-Request erzeugt wurde solange, bis sie abgefordert wird?

                  Sie wartet nicht, sie feuert das Callback.

                  Ob die Kerne der Prozessoren, die man mittles "Promises" zum Arbeiten versklavt hat nun in einer Maschine wohnen oder in getrennten müsste doch im Prinzip egal sein, oder?

                  Theoretisch ja. Praktisch ist das eine Frage, wie du zwischen den Prozessen die notwendigen Daten austauschst. Damit begibst du dich aber in ein anderes Themengebiet. Promises sind eher für Vorgänge auf einer Maschine erfunden worden. Ob das, was zu erledigen versprochen wurde, woandershin telefoniert, steht auf einem anderen Blatt außerhalb des Promises-Mustern.

                  dedlfix.

                2. Ich denke, es macht keinen Sinn, ein Promise zwischenzuspeichern. Einen XMLHttpRequest kannst Du ja auch nicht zwischenspeichern. Sobald Du die Seite verlässt, in deren Script er erzeugt wurde, verschwindet der Kontext in dem er gültig ist und alle Script-Objekte, die auf einer Seite erzeugt wurden, verschwinden im Orkus des Garbage Collectors. Nicht ohne Grund arbeitet das Webstorage API mit string/string Dictionaries.

                  Möglicherweise kannst Du mit einem Shared Worker (-> Web Worker API) etwas erreichen. Der lebt länger als eine Seite. Dann hast Du aber das Promise nicht mehr auf deinen Seiten im Zugriff, statt dessen musst Du per Messaging-Protokoll mit ihm reden. Da Objekte, die Du einem Worker schickst, geklont werden und nicht per Referenz übergeben, kannst Du auf diesem Weg kein funktionierendes Promise aus dem Worker geliefert bekommen.

                  Es sei denn, mein solides Halbwissen zum Thema Web Worker führt mich hier zu falschen Ansichten 😉

                  Rolf

                  1. Tach!

                    Ich denke, es macht keinen Sinn, ein Promise zwischenzuspeichern. Einen XMLHttpRequest kannst Du ja auch nicht zwischenspeichern.

                    Hier könnte man allerdings kombinieren. Man befragt ein Promise nach Daten. Das kann sich sofort erfüllen, wenn es die Daten mittels Storage API (local-/sessionStorage) besorgen kann, ansonsten eben später, wenn es erst einen XMLHttpRequest absetzen muss. Auf der Verwenderseite hat man lediglich die Befragung des Promise, egal woher die Daten kommen. Ohne Promise muss man sich an der Stelle zweigleisig fahren, einmal synchron mit Daten aus dem Storage, und einmal asynchron mit Daten aus dem Request.

                    dedlfix.

                    1. Das geht auf jeden Fall - aber an der Frage vorbei ;-)

                      TS will ein Promise in einem fremden Kontext nutzen - das gibt mal zumindest Javascript nicht her. Anders ist es - sagt die Wikipedia - in Sprachen, die für Nebenläufigkeit auf verteilten Systemen ausgelegt sind. Sie nennen dort die Sprachen E und Joule - von denen ICH noch nichts gehört habe. Angeblich kann die Laufzeitumgebung von E zwei Futures, deren Werte von der remote-Maschine stammen, unter gewissen Umständen in einem Roundtrip auswerten.

                      Das dürfte aber nur in einer einheitlichen Umgebung funktionieren, und es dürfte voraussetzen, dass die Kommunikation von der Laufzeitumgebung der Sprache durchgeführt wird.

                      Ist man mit der Hand am Arm unterwegs, wie in JavaScript, wo Promises "nur" eine abstrakte Hülle um beliebige asynchrone Vorgänge sind, fehlt diese Einheitlichkeit. Technisch ist es ja so, dass ein fremder Prozess, der die Antwort eines XMLHttpRequest abholen will, irgendwie auf den IP-Socket zugreifen muss, bei dem die Antwort auf den HTTP-Request ankommt. Dieser Socket "gehört" zunächst mal nicht ihm, d.h. man braucht eine Zwischenschicht, die vom XMLHttpRequest abstrahiert, den XMLHttpRequest über Seitenaufrufe hinweg am Leben hält und den Empfang der Response technisch abwickelt. Die diversen Prozesse (oder HTML-Seiten) können sich dann an die Zwischenschicht wenden und über irgendein Token die Daten abfragen. Wenn das Token serialiserbar ist (z.B. eine GUID als String), kann es von einem Prozess zum anderen übergeben werden. Aber ein Promise ist NICHT serialiserbar, weil für seine Funktion ein ganzer Schwarm von Closures erforderlich ist, die an den Kontext der laufenden HTML-Seite gebunden sind.

                      Ob die Kerne der Prozessoren, die man mittles "Promises" zum Arbeiten versklavt hat nun in einer Maschine wohnen oder in getrennten müsste doch im Prinzip egal sein, oder?

                      Ich glaube, dass diese Frage nun mit einem klaren NEIN beantwortet ist. Lokale und verteilte Programmierung benötigen grundsätzlich verschiedene Techniken. Oder hast Du schonmal aus dem Javascript im Browser eine PHP Funktion deines Servers aufgerufen? Einfach so, ohne RPC-Techniken?

                      Rolf

              2. Tach!

                in „erster Näherung“ sind Promises nur eine elegante Methode, um asynchrone Prozesse und deren Callbacks zu organisieren.

                Ja, so kann man das zusammenfassen. Promises fügen nichts neues hinzu, was man nicht auch schon vorher bei asynchronen Aufgaben mit Callbacks hätte lösen können. Sie lösen das Problem nur eleganter.

                Neu ist (aus meiner Sicht) die Möglichkeit, mit .all auf den Abschluss vieler Ereignisse zu warten.

                Ja, das wäre ohne diese Möglichkeit von Promises nur recht umständlich zu lösen. Ein Anwendungsfall wäre mehrere Requests an eine REST-API stellen zu müssen, und erst alle Ergebnisse abwarten zu müssen, um mit den Daten weiterarbeiten zu können. Man müsste in jedem einzelnen Request-Callback nachschauen, ob die anderen bereits fertig sind. Mit Promises steckt man alle Requests in einen eigenen Promise und fasst sie zusammen.

                var p1 = createRequest(someUrl); // gibt ein Promise zurück, das hier noch nicht mit then() behandelt wird
                
                Promise.all([p1, p2, p3]) // aber hier, zusammen mit den anderen analog zu p1 gestarteten.
                  .then( ... );
                

                dedlfix.

                1. Promises fügen nichts neues hinzu, was man nicht auch schon vorher bei asynchronen Aufgaben mit Callbacks hätte lösen können.

                  Weiß nicht so recht. Das Neue ist die Idee, den zukünftigen erwarteten Wert als Objekt zu repräsentieren und sich daran registrieren zu können. Dieses Objekt kann man herumreichen und sich daran für den Werteempfang registrieren. Callbacks alleine sind deutlich weniger. Will man all das tun, was Promises ermöglichen, programmiert man sie letztlich nach. Dann heißen sie vielleicht Deferred, Delay, Eventual oder Future, aber der Effekt ist ähnlich :)

                  Wobei man aufpassen muss. Die Begriffe Promise und Future sind in der Funktionalen Programmierung nicht deckungsgleich, wenn ich die engl. Wikipedia richtig deute. Erfahrung beisteuern kann ich dazu nicht, ich bin kein Erlanger, Haskeller oder Scalist, und async/await in .net habe ich bisher nicht genutzt.

                  Rolf

                  1. Tach!

                    Promises fügen nichts neues hinzu, was man nicht auch schon vorher bei asynchronen Aufgaben mit Callbacks hätte lösen können.

                    Weiß nicht so recht. Das Neue ist die Idee, den zukünftigen erwarteten Wert als Objekt zu repräsentieren und sich daran registrieren zu können. Dieses Objekt kann man herumreichen und sich daran für den Werteempfang registrieren. Callbacks alleine sind deutlich weniger. Will man all das tun, was Promises ermöglichen, programmiert man sie letztlich nach. Dann heißen sie vielleicht Deferred, Delay, Eventual oder Future, aber der Effekt ist ähnlich :)

                    Neu sind natürlich einige interessante Details, aber wenn man die nicht verwendet, ist es nicht viel mehr als ein asynchroner Callback. Neu/anders ist unter anderem, dass man auch an einem bereits erledigten Promise die done/then-Methode aufrufen kann, und die dort übergebene Callback-Funktion aufgerufen wird (in dem Fall sofort). Hängt man sich hingegen an einen Eventhandler, zum Beispiel vom XMLHttpRequest, dann wird der nicht mehr ausgelöst, wenn das Ereignis schon längst Geschichte ist.

                    dedlfix.

            3. Ich zerlege die übliche Aufrufkette mal in Fragmente:

              var prom1 = new Promise(getResource);
              
              var prom2 = prom1.done(function(data) {  // func1
                 // brassel around in the DOM and fummle data hinein
              });
              
              prom2.catch(function(reason) { // func2
                 alert("Das ging schief, weil HHTP Status " + reason); // ok, es gibt bessere Errorhandler
              });
              
              function getResource(fullfill, reject) {
                 var request = new XMLHttpRequest();
                 request.open("GET","daten.txt");}
                 request.addEventListener('loadend', function(event) {
                    if (request.status >= 200 && request.status < 300) {
                       fulfill(request.responseText);
                    } else if (request.status >= 300) {
                       reject(request.status);
                    }
              });
              

              Wo wird gewartet? Schaun wir mal.

              Die getResource-Funktion - also der Executor des Promise - wird vom Konstruktor aufgerufen. Sie schickt den HTTP Request an den Server los und registriert den loadend Handler. Dann kehrt sie zurück, und prom1 enthält nun das erste Promise. Zeit gekostet hat das nicht viel.

              Auf prom1 wird nun die done-Methode aufgerufen. Es ist damit zu rechnen, dass der HTTP Request noch eine ganze Weile auf der Leitung verbringt und noch nicht zurück ist. Also die an done übergeben Funktion in die Queue gestellt und ein neues Promise erstellt, dessen Status an das Aufrufergebnis dieser Funktion gekoppelt ist. Technisch ist das etwas vertrackter, wen's interessiert, mag die Spec lesen. Dieses Promise wird zurückgegeben. Die CPU ist nun ein paar Ticks älter, aber zum Popcorn rösten reicht es immer noch nicht.

              Auf diesem zweiten Promise wird nun noch catch aufgerufen. Das erste Promise langweilt sich immer noch, das zweite Promise etwas weniger, weil's jünger ist, und lässt den Catch-callback nun an sich pendeln. Zum Spaß gibt die ein drittes Promise zurück, aber das brauchen wir nicht mehr. Zeit? Minimal.

              Fertig - das Javascript endet und der XMLHttpRequest wartet nun friedlich auf die Serverantwort.

              Irgendwann antwortet der Server und der loadend eventListener wird aktiv. Der ruft nun entweder fulfill oder reject auf und dann…

              bei fulfill: prom1 wird erfüllt und die Function func1 wird aufgerufen. Diese gibt nichts zurück (also undefined), wodurch prom2 sich mit dem Erfüllungswert undefined erfüllt. prom2 hat keinen Handler dafür und tut weiter nichts, wodurch das vergessene prom3 sich auch erfüllt und der Ablauf endet

              bei reject: prom1 wird zurückgewiesen. Da bei prom1.done(...) kein Reject-Callback mitgegeben wurde, führt das direkt zur Zurückweisung von prom2, woraufhin die registrierte catch-Funktion prom2 aufgerufen wird. Die bringt den alert und endet ohne Rückgabe, wodurch noch das vergessene prom3 mit Erfüllungswert undefined erfüllt wird (was aber keinen interessiert).

              Zeit? Warten? Blockierendes Javascript? Nur bei Alert. Aber das war ja nur zum Spielen drin.

              Rolf

              1. Hello,

                Fertig - das Javascript endet und der XMLHttpRequest wartet nun friedlich auf die Serverantwort.

                Muss ich denn überhaupt innerhalb des X(ML)Http-Requests das Ergebnis abfragen? Ich habe die Promise-Referenz ja irgendo zwischengespeichert im Client-Dokument.

                Und das brachte mich auf die Frage, ob ich sie (odr eine Abwandlung davon) denn dann nicht auch in der Session auf dem Server ablegen könnte?

                Zugegeben, das wären dann beides wieder irgendwie globale Variablen, auch wenn Programmreferenzen o. ä. drinstehen und keine Ergebniswerte.

                Liebe Grüße
                Tom S.

                --
                Die Krawatte ist das Kopftuch des Westens
        2. Den Callback in Array.sort kannst Du aber keinesfalls durch ein Promise ersetzen, weil er ja eine Funktion darstellt, die zwei Arrayelemente vergleicht, also synchron zum Sort laufen muss.

          Etwas anderes wäre eine Sortierfunktion, die ein Promise zurückgibt, welches erfüllt ist sobald der sort fertig ist. Man könnte das Sortieren dann in einem separaten Thread durchführen und währenddessen andere nützliche Arbeit tun. Zum Beispiel ein zweites Array sortieren.

          Wäre doch ein nettes Beispiel. Angenommen, ich hätte eine Funktion Array.sortAsync, die ein Array in einem Hintergrundthread sortieren kann (wie auch immer die das tut - das ist jetzt wurscht) und mir ein Promise zurückgibt. Diese Funktion ist nicht Teil von JS, sondern nur ein Gedankenbeispiel. Dann könnte ich schreiben:

          var myArray = [ 4,7,1,1];
          var result;
          Array
             .sortAsync(myArray)              // HYPOTHETISCHE FUNKTION !!!
             .done(function(sorted) {
                 console.log(sorted[0]);      // Wäre machbar
                 result = sorted;
             });
             // Hier geht's schon weiter, während noch sortiert wird!
             // deshalb kommt ein Fehler, dass result undefiniert sei
             console.log(result[0]);
          

          Das ist ja ganz nett und zeigt auch den ersten Fallstrick, aber es ist nicht wirklich sinnvoll, weil ich ja nix sinnvolles tue während der Sort läuft. Anders wäre sowas hier:

          var myArray = [ 4, 7, 1, 1 ], yourArray = [ 0, 8, 1, 5 ];
          var mySortedArray, yourSortedArray;
          var promise1 = Array.sortAsync(myArray).then(function(s) { mySortedArray = s; });;
          
          var yourSortedArray = Array.sort(yourArray);
          
          // Und jetzt? mySortedArray und yourSortedArray vergleichen?
          

          Das ist so noch nicht unbedingt brauchbar. Wenn Du mySortedArray und yourSortedArray unabhängig voneinander weiterverarbeiten kannst, dann kann man es so lassen. Aber wenn du z.B. beide vergleichen willst, brauchst Du eine Synchronisierung beider Sortierungen, und dafür verwendet man wieder ein Promise:

          var myArray = [ 4, 7, 1, 1 ], yourArray = [ 0, 8, 1, 5 ];
          
          var promise1 = Array.sortAsync(myArray);
          var promise2 = Array.sortAsync(yourArray);
          
          Promise.all([promise1,promise2]).done(function(werte) {
             var mySortedArray = werte[0];
             var yourSortedArray = werte[1];
             // Arrays im done-Handler verarbeiten
          });
          

          Promise.all liefert dir ein DRITTES Promise, dass sich in dem Moment erfüllt wo alle Teilpromises erfüllt sind.

          Rolf

          1. Tach!

            Den Callback in Array.sort kannst Du aber keinesfalls durch ein Promise ersetzen, weil er ja eine Funktion darstellt, die zwei Arrayelemente vergleicht, also synchron zum Sort laufen muss.

            Das war auch nicht meine Absicht. Nichtsdestotrotz ist diese Funktion ebenfalls ein Callback. Der Aufrufer wird zurückgerufen, um einen Teil der Aufgabe zu erledigen. Beim Sortieren/Finden passiert das mehrfach während der Arbeit, beim Promise einmal danach. Oder genauer gesagt, einmal als einer der letzten Schritte der Arbeit.

            Etwas anderes wäre eine Sortierfunktion, die ein Promise zurückgibt, welches erfüllt ist sobald der sort fertig ist. Man könnte das Sortieren dann in einem separaten Thread durchführen und währenddessen andere nützliche Arbeit tun. Zum Beispiel ein zweites Array sortieren.

            Scheitert bei Javascript daran, dass es üblicherwiese nicht mit mehreren Thread daherkommt. Aber du kannst dein Beispiel auf die Abfrage von Daten vom Server umbauen, dann wird es realistischer.

            Aber ansonsten ist das Beispiel brauchbar, um zu zeigen, dass globale Variablen, die in einem Callback befüllt werden, konzeptionell Mist sind. Der Zugriff darauf darf und kann erst im Callback oder in davon aufgerufener Folgeverarbeitung erfolgen. Eine solche Variable global anzulegen, klappt nur, wenn sie in einem Promise gekapselt ist, das erst bei Befüllung erfüllt wird.

            Promises sind jedenfalls ein wichtiger Grundpfeiler für asynchron arbeitende Anwendungen.

            dedlfix.

            1. Ich habe den bisher rudimentären Promises Wiki-Artikel von Matthias Scharwies mal etwas erweitert, als Einstieg für einen ordentlichen Ausbau. Ein Tutorial ist es nicht geworden, aber zumindest steht da das drin, was ich hier auch geschrieben habe.

  2. Also was Du da in Node und Mongo machst, davon hab ich ja keine Ahnung, aber rein theoretisch betrachtet sind Promises ein Konzept der funktionalen Programierung, das Eingang in ECMAScript gefunden hat.

    Leider ist das Promise nur rudimentär in unserem Wiki beschrieben, da ist noch was zu tun. Mal gucken ob ich dazu komme. Unterscheiden muss man auch zwischen den ECMAScript 6 Promises, und diversen Vorläufern aus Javascript-Bibliotheken, die ähnliches tun (z.B. Deferred in jQuery).

    Abstract: Promises formalisieren das Handling asynchroner Abläufe. Dazu nutzen sie Callback-Funktionen.

    Langfassung:

    Ein Promise in JavaScript ist eine formalisierte Vorgehensweise, um Reaktionen auf asynchrone Abläufe zu realisieren. Callbacks tauchen dabei als Implementierungsdetail auf. Ein Promise-Objekt bekommt einen Executor mit, der vom Promise-Konstruktor seinerseits zwei Callbacks ins Promise bekommt (resolve und reject). Der Executor tut irgendwas, und entscheidet am Ende, ob er den resolve oder reject Callback aufruft.

    Dieser Teil ist Aufgabe des Promise-Lieferanten.

    Nun zur Sicht des Promise-Empfängers. Der ruft irgendwas auf, was ein Promise zurückgibt. Dieses Promise gilt zunächst einmal - solange der Executor des Lieferanten weder resolve noch reject gerufen hat - als „pending“ - Antwort offen. Sobald der Executor eine der beiden Callbackfunktionen gerufen hat, ist das Promise entweder erfüllt (fulfilled) oder zurückgewiesen (rejected). MDN übersetzt hier „fehlgeschlagen“, weiß nicht was nun besser ist.

    Um nun vom Lieferanten den versprochenen Wert zu erhalten, ruft der Empfänger die .done Methode auf dem Promise auf. Dieser übergibt er wieder eine Callback-Funktion, die vom Promise in dem Moment aufgerufen wird, wo das Promise sich erfüllt. Das, was der Executor an resolve() übergeben hat, wird nun an den done-Callback übergeben.

    Um benachrichtigt zu werden, ob das Versprechen gebrochen wurde, gibt es noch die .catch-Methode, hier kann man einen Callback registrieren, der aufgerufen wird wenn der Zustand des Promise auf rejected wechselt.

    Im Gegensatz zum klassischen Callback können einem Promise mehrere done-Handler übergeben werden. Diese bilden eine Queue, die (vermutlich) in Registrierungsreihenfolge abgearbeitet werden. Ein done-Handler kann seinerseits ein neues Promise zurückgeben, mit eigenem Executor, so dass man then-Aufrufe verketten kann.

    promiseMe(executor1) .done(doneHandler1ThatReturnsPromise2) .done(doneHandler2ForPromise2);

    Wie man sich das Chaos vorstellen soll, wenn auf ein Promise mehrere done-Handler registriert werden und jeder ein eigenes neues Promise zurückliefert, das müsste ich erstmal in Ruhe entwirren.

    Spannend am Promise ist auch, dass es nach seiner Erstellung als Objekt existiert und weitergereicht werden kann. Jeder, der das Promise in die Hand bekommt, kann dann einen done- oder catch-Handler anhängen. Mit einem einfachen Callback-Mechanismus geht so etwas nicht.

    Rolf

  3. Promise kann ich Dir nicht erklären, das raff' ich nicht in meiner derzeitigen Verfassung. Aber was eine Callback-Funktion ist, schon:

    Eine solche bietet sich immer an, wenn sich damit die Datenerfassung von der Datenverarbeitung/Datenausgabe zweckmäßig trennen lässt. Beispiel SQL, Abfrage => Ausgabe:

    Dem Abfrageprozess wird eine Referenz auf die Callbackfunktion übergeben. Jedesmal wenn der Abfrageprozess eine neue Zeile liefert, wird mit den Daten der jeweiligen Zeile die Callbackfunktion aufgerufen. Letzere schreibt dann die Zeile bspw. nach stdout oder in eine Datei.

    Die Zweckmäßigkeit wird sofort klar, ist eine gepufferte Ausgabe. Des Weiteren muss der Abfrageprozess nicht auf den Ausgabeprozess warten, ja es kann durchaus vorkommen, das der Abfrageprozess längst beendet ist und der Ausgabeprozess noch läuft, die Prozesse werden entkoppelt.

    MfG

    1. Tach!

      Promise kann ich Dir nicht erklären, das raff' ich nicht in meiner derzeitigen Verfassung. Aber was eine Callback-Funktion ist, schon:

      Eine solche bietet sich immer an, wenn sich damit die Datenerfassung von der Datenverarbeitung/Datenausgabe zweckmäßig trennen lässt. Beispiel SQL, Abfrage => Ausgabe:

      Dem Abfrageprozess wird eine Referenz auf die Callbackfunktion übergeben. Jedesmal wenn der Abfrageprozess eine neue Zeile liefert, wird mit den Daten der jeweiligen Zeile die Callbackfunktion aufgerufen. Letzere schreibt dann die Zeile bspw. nach stdout oder in eine Datei.

      Die Zweckmäßigkeit wird sofort klar, ist eine gepufferte Ausgabe. Des Weiteren muss der Abfrageprozess nicht auf den Ausgabeprozess warten, ja es kann durchaus vorkommen, das der Abfrageprozess längst beendet ist und der Ausgabeprozess noch läuft, die Prozesse werden entkoppelt.

      Eine Zweckmäßigkeit kann nur beurteilt werden, wenn man sie anhand der Aufgabenstellung untersucht. Du hast hier lediglich eine Möglichkeit beschrieben, die für sich genommen weder zweck- noch unzweckmäßig ist. Eine klassische Kombination aus Query und Fetch-Schleife mit darin befindlicher Ausgabe kann genauso zweck- oder unzweckmäßig sein - je nach Anforderung.

      Außerdem ist das, was du beschreibst, keineswegs eine gepufferte Ausgabe, wenn die Ausgabe sofort für jeden Datensatz ausgeführt wird. Was macht denn, während die Callback-Funktion die Ausgabe erledigt, die Abfrage? Natürlich nichts. Deren Verarbeitung ist angehalten, bis der Callback beendet ist, weil die CPU mit diesem geschäftigt ist. Es sei denn, die Callback-Funktion wird in einem eigenen Thread ausgeführt. Aber auch dann sehe ich nicht, wo da was gepuffert sein soll. Die Ausgabe wäre in dem Fall lediglich parallelisiert, ohne dass da ein Puffer im Spiel ist.

      Gepuffert wäre die Angelegenheit, wenn du ein Array mit den abgefragten Datensätzen erzeugst und dieses erst anschließend dem Ausgabeteil übergibst. Das Array wäre dann der Puffer.

      dedlfix.