Uri: Programmfluss Node.js (asynchron) regeln

Hallo,

ich bastle noch immer an meiner Webapp und mir macht das asynchrone Verhalten von node.js nach langem mal wieder zu schaffen. Ich war schon so weit, dass ich verschachtelung von Callbacks eine gewisse Kontrolle hinbekommen habe, aber meine Funktionen waren bisher nicht so wahnsinnig komplex.

Ich versuche das Problem mal etwas genauer zu beschreiben, da aus dem Code-Schnipsel unten nicht genau erkennbar wird was ich eigentlich machen möchte:

Ich habe unter anderem folgende Tabellen in meine Datenbank:

Hauskosten( typ_id, Haus_id, jahr, Kosten)

Hauskostentypen( id, Bezeichnung)

Dann habe ich ein langes Formular mit viele Zeilen, wo jeweils ein Select und als Option alle Typbezeichnungen sind und die in Input für die Kosten.

Serverseitig möchte ich dann für jeden Wertepaar(Bezeichnung, kosten) fragen: gib mir die typ_id, wo die bezeichnung x ist und dann füge in typ_id, kosten, jahr und Haus_id) in die Datenbank hinzu. und das eben für mehrere Felderpaare.

Meine Idee war das ganze mit einer Schleife zu Kontrollieren, aber ich weiß nicht wie. Ich habe auch versucht Promises zu verwenden aber damit funktioniert es auch nicht.

//Store house_costs
app.post("/buildings/:id/add_costs", function(req, res){
  var titles = req.body.cost_positions.titles;
                    
  var year = req.body.year;
  var building_id = req.body.building_id;
  var costs = req.body.cost_positions.costs;
          
          var get_house_cost_type_id = function(titles, index) { 
              return new Promise(function(resolve, reject){
                var type;
                var query = "SELECT id FROM re_manager.house_cost_types WHERE designation='"+titles[index]+"';";
                db.execute(query, function(err, result) {
                  var type = result[0].id;
                  resolve(type);
                });
            });
          }
          
          var insert_house_cost = function(type, building_id, year, costs, index){
            return new Promise(function(resolve, reject){
              var query = "INSERT INTO re_manager.house_costs( type, building_id, year, costs)VALUES("+type+", "+building_id+ ", "+year+ ", "+ costs[index]+ ");";
              db.execute(query,function(err) {
                if (err) throw err;
                resolve("inserted row");
              });
              
            });
          }
          var insert_values_loop = function(building_id, year, costs, titles){
            return new Promise(function(resolve, reject){
              for(var i=0; i<titles.length; i++){
                get_house_cost_type_id(titles, i).then(function(type){
                  insert_house_cost(type, building_id, year, costs, i).then(function(result){
                    console.log(result);
                    
                  });
                });
              }
              resolve("inserted_all rows;");
            });
          }
          
    insert_values_loop(building_id, year, costs, titles).then(function(result){
        console.log(result);
        res.redirect("/buildings/"+ building_id);
    });
});

Ich bin noch nicht ganz so fit, was Promises angeht, aber mit Callbacks innerhalb einer for-Schleife ging das eben auch nicht.

Danke auf jeden Fall schon mal im Voraus.

Gruß Uri

  1. Tach!

    Meine Idee war das ganze mit einer Schleife zu Kontrollieren, aber ich weiß nicht wie.

    Du möchtest also alle Aufgaben erledigt haben und dann erst das insert_values_loop-Promise erfüllen. Dafür gibt es Promise.all(). Und wenn das nicht reicht, gibt es mit ReactiveX noch eine ganze Menge mehr Möglichkeiten, als simple Promises leisten können. Aber probier es erstmal mit dem all().

    dedlfix.

    1. Hi, Danke für die Antwort. Ich habe mit promise.all() insofern ein verständnisproblem, dass ich nicht ganz verstehe, wie man durchiterieren soll

      Ich sende zwei Arrays an den Server. titles und costs. In titles[0] steht zum Beispiel "Warmwasser" in costs[0] stehen die kosten vom Warmwasser.

      nun möchte ich:

      1. von meine Haushostentypen-tabelle wissen welche type_id Warmwasser hat
      2. type_id und Kosten in meine Hauskostentabelle speichern

      Das funktioniert, wenn ich einen expliziten Index, wie oben titles[0], costs[0] verwende.

      Wenn ich das mit callback schreibe

      dann sieht es etwa so aus:

      db.execute(get_id_query, function(){
         db.execute(insert_costs_query, function(){
             //do something after insert
         }
      });
      

      Mit Promises

      get_house_cost_type_id(titles, 0).then(function(type){
         insert_house_cost(type, building_id, year, costs, 0).then(function(result){
            console.log(result);
         });
      });
      

      Und mit Promise.all() so

      Promise.all([get_house_cost_type_id(titles,0)]).then(function(type){
          Promise.all([insert_house_cost(type, building_id,year, costs, 0)]).then(function(result){
                   
         });
      });
      

      Da habe ich zum einen das Problem, dass ich erst im Callback die Andere Funktion aufrufen kann, weil ich als parameter die id brauche. zum anderen verstehe ich nicht, wie ich das eben für alle values in titles und costs durchführen kann.

      Habe auch überlegt sowas zu machen, aber damit ging es leider auch nicht:

      var i= 0;
      while(i < titles.length){
         Promise.all([get_house_cost_type_id(titles,i)]).then(function(type){
            Promise.all([insert_house_cost(type, building_id,year, costs, i)]).then(function(result){
                   i++;
            });
         });
      }
      

      Vielleicht stell ich mich gerade aber auch doof an. Wie genau soll das mit all gehen.

      Beste Grüße uri

      1. Tach!

        Ich habe mit promise.all() insofern ein verständnisproblem, dass ich nicht ganz verstehe, wie man durchiterieren soll

        Du erzeugt die Promises und fügst sie einem Array hinzu. Dieses übergibst du dann dem Promise.all().

        dedlfix.

        1. Hi,

          ja, aber in dem Array, dass ich übergebe, wäre der zweite Wert vom Ergebnis des ersteren Wertes abhängig und müsste als Parameter übergeben werden. Das verwirrt mich ja.

          Also ganz abstrahiert:

          var a = function(){ return n; }
          var b = function (n) { ... }
          
          Promise.all([ a(), b(n) ]).then(function(){});  //wie bekomme ich an dieser stelle n?
          
          

          Das andere Problem, falls ich das hinkriege: Wie führe ich a() und b(n) für jeden Eintrag in meinem Array aus der Form durch.

          Danke und Beste Grüße

          Uri

          1. Tach!

            ja, aber in dem Array, dass ich übergebe, wäre der zweite Wert vom Ergebnis des ersteren Wertes abhängig und müsste als Parameter übergeben werden.

            Die Vorgänge (get_house_cost_type_id und insert_house_cost) als solche sind doch abgeschlossen, oder müssen sie auf irgendwas warten, außer dass du sie startest und der eine auf den anderen? Dann bau ein Promise drumherum, das vom insert_house_cost()-Promise-Resultat erfüllt wird. Diese Promises kannst du dann ins Array packen.

            Es hilft da, modular zu denken. Du hast viele Vorgänge, bestehend aus zwei Teilvorgängen. Die sind alle gleich, unterscheiden sich nur durch die zu verarbeitenden Werte. Also kapsel den Vorgang in einem Modul (im allgemeinen Wortsinn, nicht im "import … from …"-Sinne), zum Beispiel in einer Funktion. Oder hier in einem Promise, wegen der Asynchronität. Und nun kannst du diese Vorgänge starten, wie es beliebt. Zum Beispiel nur einen einzigen, oder auch viele in einer Schleife, oder über ein Promise.all()-Array. (Achja, und übergib den eigentlichen Wert, nicht ein ganzes Array plus Index-Nummer.)

            dedlfix.

  2. Moin,

    Ich bin noch nicht ganz so fit, was Promises angeht, aber mit Callbacks innerhalb einer for-Schleife ging das eben auch nicht.

    Ohne jetzt genau gelesen zu haben sind Asynchhrone Schleifen eingentlich einfach umzusetzen.

    asyncForEach: function ( obj, toDo, callback ) {
    		var i = 0, k = Object.keys( obj ), v = Object.values( obj );
    		
    		function check ( b = false ) {
    			i++;
    			
    			if ( i === Object.keys( obj ).length || b === true ) {
    				if ( callback !== undefined ) {
    					callback();
    				}
    			} else {
    				toDo( k[i], v[i], check );
    			}
    		}
    		
    		toDo( k[i], v[i], check );
    	}
    

    So sieht jedenfalls meine asynchrone foreach Schleife aus. Ein aufruf wäre dann z.B.

    module.asyncForEach( myObj, function ( key, value, check ) {
    	// Do something
    	check();
    } , funcction(){
    	// Do something when finished the loop
    });
    

    Aber zusätzlich ein Tipp: du solltest dich unbedingt mit einer anderen Datenbank auseinandersetzen. Für Node.js Apps solltest du z.B. MongoDB verwenden, nicht nur, dass es wesentlich performanter ist als MySQL sondern auch gleich den Vorteil hat, dass du die Objekte die du erzeugst/bearbeitest direkt in die DB schreibst und somit auch einfach wiederbekommst.

    Gruß
    Jo

    1. Tach!

      Ich bin noch nicht ganz so fit, was Promises angeht, aber mit Callbacks innerhalb einer for-Schleife ging das eben auch nicht.

      Ohne jetzt genau gelesen zu haben sind Asynchhrone Schleifen eingentlich einfach umzusetzen.

      Deine Lösung ist in dem Fall nicht verwendbar. Dein Callback wird aufgerufen, wenn die Schleife abgearbeitet ist. In seinem Fall handelt es sich aber um Promises, also asynchrone ToDos. Das äußere Promise darf erst erfüllt werden, wenn die anderen Promises erfüllt sind. Bezogen auf deine Funktion darf das Callback erst aufgerufen werden, wenn alle asynchronen ToDos ihrerseits ihr Callback aufgerufen haben.

      		var i = 0, k = Object.keys( obj ), v = Object.values( obj );
      		function check ( b = false ) {
      

      Die Bedeutung von i, k und v kann man sich denken, aber was ist die Aufgabe von b? Dem wird nie ein anderer Wert zugewiesen. Vermutlich hast du dessen Verwendung aus dem Beispiel rausgekürzt. Aber nicht sprechende Variablennamen sind schlecht fürs Verständnis. Wenn break nicht geht, weil es ein reserviertes Wort ist, dann gibt es noch ein paar Synonyme, beispielsweise cancel.

      				if ( callback !== undefined ) {
      					callback();
      				}
      

      undefined darf der Callback nicht sein, aber null, 42 oder "foo" sind ok?

      dedlfix.

      1. Moin,

        Deine Lösung ist in dem Fall nicht verwendbar. Dein Callback wird aufgerufen, wenn die Schleife abgearbeitet ist. In seinem Fall handelt es sich aber um Promises, also asynchrone ToDos. Das äußere Promise darf erst erfüllt werden, wenn die anderen Promises erfüllt sind. Bezogen auf deine Funktion darf das Callback erst aufgerufen werden, wenn alle asynchronen ToDos ihrerseits ihr Callback aufgerufen haben.

        check() sollte natürlich auch erst aufgerufen werden wenn der asynchrone Task beendet ist, zum Beispoiel im Callback einer MySQL abfrage. Dachte das ist ersichtlich.

        		var i = 0, k = Object.keys( obj ), v = Object.values( obj );
        		function check ( b = false ) {
        

        Die Bedeutung von i, k und v kann man sich denken, aber was ist die Aufgabe von b? Dem wird nie ein anderer Wert zugewiesen. Vermutlich hast du dessen Verwendung aus dem Beispiel rausgekürzt. Aber nicht sprechende Variablennamen sind schlecht fürs Verständnis. Wenn break nicht geht, weil es ein reserviertes Wort ist, dann gibt es noch ein paar Synonyme, beispielsweise cancel.

        Ob das jetzt b oder cancel heißt, wo ist der unterschied? Falls check( true ) aufgerufen wird bricht die Schleife ab und es wird der Callback aufgerufen.

        				if ( callback !== undefined ) {
        					callback();
        				}
        

        undefined darf der Callback nicht sein, aber null, 42 oder "foo" sind ok?

        Wenn ich jemals einen Intenger oder String als Funktion aufrufen sollte, geb ich dir bescheid. Hm, ja null, doofe Sache, werd ich mal ändern, also ( typeof( callback ) === 'function' ).

        Gruß
        Jo

        1. Hallo J o,

          Ob das jetzt b oder cancel heißt, wo ist der unterschied?

          cancel ist ein sprechender Variablenname, b nicht.

          Bis demnächst
          Matthias

          --
          Rosen sind rot.
          1. Hey,

            Ob das jetzt b oder cancel heißt, wo ist der unterschied?

            cancel ist ein sprechender Variablenname, b nicht.

            i auch nicht, dann ab jetzt bitte immer for( var iterator = 0; iterator < length; iterator++ ){}.

            Sicher sagt cancel mehr aus als b, aber ich denke doch, dass die Semantik nicht vorgeschrieben ist und jeder für sich entscheiden darf wie er seine Variablen benennt.

            Gruß
            Jo

            1. Tach!

              i auch nicht, dann ab jetzt bitte immer for( var iterator = 0; iterator < length; iterator++ ){}.

              i (und j) haben sich als eingebürgert als Iteratorvariablen. Schön ist das nicht unbedingt, aber deren Bedeutung ist eigentlich jedem klar.

              Sicher sagt cancel mehr aus als b, aber ich denke doch, dass die Semantik nicht vorgeschrieben ist und jeder für sich entscheiden darf wie er seine Variablen benennt.

              Es existiert auch keine Vorschrift, wie du Laute bilden und was für Zeichen du verwenden und aneinanderhängen darfst. Du tust das trotzdem in einer bestimmten Art und Weise, weil du verstanden werden möchtest. Warum also nicht auch bei Code?

              dedlfix.

              1. Hey,

                Es existiert auch keine Vorschrift, wie du Laute bilden und was für Zeichen du verwenden und aneinanderhängen darfst. Du tust das trotzdem in einer bestimmten Art und Weise, weil du verstanden werden möchtest. Warum also nicht auch bei Code?

                Wie ich dir eben schon geantwortet habe, Code ist nicht dafür da das Menschen ihn lesen können sondern dafür das die Maschine ihn lesen kann und das ist unabhängig vom Namen der Variable.

                Gruß
                Jo

                1. Hallo,

                  Wie ich dir eben schon geantwortet habe, Code ist nicht dafür da das Menschen ihn lesen können sondern dafür das die Maschine ihn lesen kann und das ist unabhängig vom Namen der Variable.

                  Und wenn du im Vorstellungsgespräch gefragt wirst, ob du teamfähig bist, antwortest du genauso überzeugt: "nein, natürlich nicht!"…

                  Gruß
                  Kalk

                2. Wie ich dir eben schon geantwortet habe, Code ist nicht dafür da das Menschen ihn lesen können sondern dafür das die Maschine ihn lesen kann und das ist unabhängig vom Namen der Variable.

                  https://de.wikipedia.org/wiki/Obfuscated_Perl_Contest#Beispiel

                  Debug das mal!

                  1. Hey,

                    https://de.wikipedia.org/wiki/Obfuscated_Perl_Contest#Beispiel

                    Debug das mal!

                    Warum? ich hab das nicht geschrieben und weiß nicht was der Code macht / machen soll. Außerdem hab ich absolut keine Ahnung von PERL. Aber schön, dass sowas Ausgezeichnet wird ;)

                    Gruß
                    Jo

                    1. Warum? ich hab das nicht geschrieben und weiß nicht was der Code macht / machen soll. Außerdem hab ich absolut keine Ahnung von PERL. Aber schön, dass sowas Ausgezeichnet wird ;)

                      Es freut mich, dass Du die Idee dahinter verstanden hast!

            2. Hallo J o,

              Ob das jetzt b oder cancel heißt, wo ist der unterschied?

              cancel ist ein sprechender Variablenname, b nicht. Sicher sagt cancel mehr aus als b, aber ich denke doch, dass die Semantik nicht vorgeschrieben ist und jeder für sich entscheiden darf wie er seine Variablen benennt.

              Du hast gefragt, ich habe geantwortet. Nicht mehr und nicht weniger. Natürlich darfst du deinen Code so schreiben, wie du möchtest, falls es keine Team-Vorgaben gibt.

              Bis demnächst
              Matthias

              --
              Rosen sind rot.
        2. Tach!

          check() sollte natürlich auch erst aufgerufen werden wenn der asynchrone Task beendet ist, zum Beispoiel im Callback einer MySQL abfrage. Dachte das ist ersichtlich.

          War es (mir) anscheinend nicht. Aber ja, so kann es gehen. Jedenfalls, wenn man nicht bereits mit Promises arbeitet oder mit Funktionen, die Promises liefern. In dem Fall wäre es sinnvoller, bei den Mechanismen der Promises zu bleiben, die ja eine Antwort auf das Problem bereits mitbringen.

          Ob das jetzt b oder cancel heißt, wo ist der unterschied? Falls check( true ) aufgerufen wird bricht die Schleife ab und es wird der Callback aufgerufen.

          Und das muss ich erst aus dem Code lesen, denn ein einfaches b sagt mir das nicht. Voraussetzung ist auch, dass der Code das macht, was er soll. Wenn da ein Fehler drin ist, kann unter Umständen die eigentliche Intention aus solchen Abkürzungen nicht hervorgehen. Das macht es dann nicht unbedingt leichter, den Fehler zu eliminieren.

          dedlfix.

          1. Moin,

            check() sollte natürlich auch erst aufgerufen werden wenn der asynchrone Task beendet ist, zum Beispoiel im Callback einer MySQL abfrage. Dachte das ist ersichtlich.

            War es (mir) anscheinend nicht. Aber ja, so kann es gehen. Jedenfalls, wenn man nicht bereits mit Promises arbeitet oder mit Funktionen, die Promises liefern. In dem Fall wäre es sinnvoller, bei den Mechanismen der Promises zu bleiben, die ja eine Antwort auf das Problem bereits mitbringen.

            Gut, ich habe noch nicht mit Promises gearbeitet da ich meine Asynchronen Funktionen eben mit Callbacks oder dieser "Wasserfall"-Schleife abgearbeitet habe.

            Ob das jetzt b oder cancel heißt, wo ist der unterschied? Falls check( true ) aufgerufen wird bricht die Schleife ab und es wird der Callback aufgerufen.

            Und das muss ich erst aus dem Code lesen, denn ein einfaches b sagt mir das nicht. Voraussetzung ist auch, dass der Code das macht, was er soll. Wenn da ein Fehler drin ist, kann unter Umständen die eigentliche Intention aus solchen Abkürzungen nicht hervorgehen. Das macht es dann nicht unbedingt leichter, den Fehler zu eliminieren.

            Natürlich muss man das aus dem Code lesen was eine Funktion tut, wenn man denn nicht weiß was sie tut oder tun soll. Bei mir sieht die Funktion auch etwas anders aus, ich hab sie ja schon für die Leserlichkeit etwas aufgeweitet:

            aFE: function(o,d,c){var i=0,k=Object.keys(o),v=Object.values(o);function p(b=false){i++;if(i===Object.keys(o).length||b===true){if(typeof(c)==='function'){c();}}else {d(k[i],v[i],p);}}d(k[i],v[i],p);}
            

            Aber ihr habt Recht, nur weil ich weiß was mein Code tut, heißt das nicht das ihr ihn verständlich lesen könnt. Aber dafür ist Code auch nicht da, dass er Menschen lesbar ist und sich jeder direkt darunter etwas vorstellen kann, denn Code ist und bleibt eine Maschinen Sprache. Erst wenn es um das lernen bzw. erklären geht, muss Code greifbar und verständlich lesbar sein.

            Gruß
            Jo

            1. Tach!

              Aber ihr habt Recht, nur weil ich weiß was mein Code tut, heißt das nicht das ihr ihn verständlich lesen könnt. Aber dafür ist Code auch nicht da, dass er Menschen lesbar ist und sich jeder direkt darunter etwas vorstellen kann, denn Code ist und bleibt eine Maschinen Sprache. Erst wenn es um das lernen bzw. erklären geht, muss Code greifbar und verständlich lesbar sein.

              Das sehe ich anders. Code wird von Menschen geschrieben und von ihnen gepflegt. Deswegen muss er menschenlesbar sein. Nicht uneingeschränkt, aber für diejenigen, die die jeweilige Sprache beherrschen. Die Maschine muss den Code auch lesen können, aber nur, um ihn in die eigentlichen Maschinenbefehle übersetzen zu können. Ihr ist es dabei egal, ob kryptische oder sprechende Bezeichner verwendet werden. Aber gerade weil er menschenlesbar sein soll, verwenden wir solche auf das menschliche Verständnis ausgelegten Hochsprachen und mühen uns nicht mit den Maschinenbefehlen ab.

              In vielen Fällen ist man selbst derjenige, der den eigenen Code erneut lernen muss, weil Vergessen eine unabdingbare Eigenschaft unseres Gedächtnisses ist. Insofern hast du quasi selbst ein Argument geliefert, verständlichen Code zu schreiben.

              dedlfix.

              1. Hey,

                In vielen Fällen ist man selbst derjenige, der den eigenen Code erneut lernen muss, weil Vergessen eine unabdingbare Eigenschaft unseres Gedächtnisses ist. Insofern hast du quasi selbst ein Argument geliefert, verständlichen Code zu schreiben.

                Nun, ich kann meinen Code auch so lesen und verstehen und auch nach längerer Zeit noch nachvollziehen. Vielleicht weil ich mir einfach angewöhnt habe so zu schreiben.

                Gruß
                Jo

                1. Hallo J o,

                  Nun, ich kann meinen Code […] auch nach längerer Zeit noch nachvollziehen.

                  Das wünsch ich dir von ganzem Herzen. Wirklich.

                  Bis demnächst
                  Matthias

                  --
                  Rosen sind rot.
            2. @@J o

              Aber ihr habt Recht, nur weil ich weiß was mein Code tut, heißt das nicht das ihr ihn verständlich lesen könnt. Aber dafür ist Code auch nicht da, dass er Menschen lesbar ist und sich jeder direkt darunter etwas vorstellen kann, denn Code ist und bleibt eine Maschinen Sprache. Erst wenn es um das lernen bzw. erklären geht, muss Code greifbar und verständlich lesbar sein.

              Kam gerade mal wieder über den Ticker:

              “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” —Martin Fowler

              LLAP 🖖

              --
              „Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann