Klaus1: Verständnisproblem: MySQL-Queries in Node.js

Hallo,

ich verwende node.js mit Library promise-mysql, da ich voneinander abhängige Queries benötige, also nicht vollkommen aynchron laufen dürfen.

Soweit so gut, das funktioniert erstmal so:

pool = mysql.createPool(mysqlConnData);

param1 = 23;
param2 = 70;
param3 = 163; 
param4 = 295;

console.log("Start");

	pool.getConnection()
	.then(function(conn){
		connection = conn;
		var result = connection.query('select feldtyp from werte where idnr = ?', param1);
		return result;
	}).then(function(rows){
		console.log("1. "+rows[0].feldtyp);
		var result = connection.query('select feldtyp from werte where idnr = ?', param2);
		return result;
	}).then(function(rows){
		console.log("2. "+rows[0].feldtyp);
		var result = connection.query('select feldtyp from werte where idnr = ?', param3);
		return result;
	}).then(function(rows){
		console.log("3. "+rows[0].feldtyp);
		var result = connection.query('select feldtyp from werte where idnr = ?', param4);
		return result;
	}).then(function(rows){
		console.log("4. "+rows[0].feldtyp);
		connection.release();
		return;
	}).catch(function(error){
		if (connection && connection.release) connection.release();
		//logs out the error
		console.log(error);
	});

console.log("fertig");

Dabei wird folgende Ausgabe erzeugt:

Start
fertig
1. datum
2. zeit
3. text
4. farbe

Das schon fertig ausgegeben wird, vor den Datenbank-Abfragen, habe ich so verstanden, dass die Abfragen asynchron ausgeführt werden und die Abfrage nicht so schnell ist wie die weitere Ausführung des Programms.

Dann habe ich das Script angepasst und das Ganze in eine While-Schleife gepackt:

i = 0;
while (i < 5) {
	i ++;
	console.log("Durchlauf Nr. "+i);
	pool.getConnection()
	...

Das hatte dann die folgende Ausgabe zur Folge:

Start
Durchlauf Nr. 1
Durchlauf Nr. 2
Durchlauf Nr. 3
Durchlauf Nr. 4
Durchlauf Nr. 5
fertig
1. datum
2. zeit
3. text
4. farbe
1. datum
2. zeit
3. text
4. farbe
1. datum
2. zeit
3. text
4. farbe
1. datum
2. zeit
3. text
4. farbe
1. datum
2. zeit
3. text
4. farbe

Jetzt könnte es zwar noch sein, dass es immer noch zu schnell wäre, aber selbst bei 1000 sieht das Ergebnis so aus. Das verstehe ich schon nicht mehr so ganz. Ich hätte erwartet, dass zwischendurch schonmal eine Ausgabe der MySQL-Abfrage durchrutscht.

Ich habe darauf das Script nochmal leicht angepasst:


i = 0;
while (i < 1000) {
	pool.getConnection()
	.then(function(conn){
		i++;
		console.log("Durchlauf Nr. "+i);
		...

Wenn ich das Script jetzt ausführe, wird "Start" ausgegeben, einige Zeit passiert erstmal nichts mehr und dann bricht das Script mit einem "Javascript heap out of memory" ab.

Wieso läuft die Ausführung jetzt gar nicht mehr in die MySQL-Abfragen rein?

Ich stehe da wohl total auf dem Schlauch und hoffe, ihr könnt etwas Licht ins Dunkle bringen.

LG Klaus

  1. Hallo Klaus1,

    der zeitliche Ablauf ist so, dass in der While-Schleife die getConnection-Aufrufe laufen. Diese erfolgen asynchron, richtig, die entsprechenden Aufrufe gehen an den SQL Treiber und die Promises registrieren sich intern auf entsprechende Callbacks. Da Du die Queries jeweils in einem eigenen then-Handler durchführst, sind sie säuberlich serialisiert.

    Nun ist es wichtig zu wissen, dass Node.js auf Google V8 aufbaut und darum wohl genau so single-threaded ist. D.h. eine Verarbeitung der Callbacks beginnt erst, wenn das Hauptprogramm fertig ist.

    Deine Callbacks warten also, bis die Schleife fertig ist. Wenn Du i in einem Callback erhöhst, wird es erst erhöht, wenn die Schleife fertig ist. Die wird aber nicht fertig. Weil das Erhöhen von i auf das Ende der Schleife wartet. Deswegen forderst Du neue Connections an, bis der Heap platzt.

    Wenn Du i außerhalb der then-Handler erhöhst, hast Du ein anderes Problem. WEIL die Callbacks erst loslaufen, wenn die Schleife zu Ende ist, findet jeder von ihnen i=6 oder i=1001 vor (je nach Schleifengrenze). Und die connection-Variable wird kreuz und quer überschrieben. Brrr.

    Wenn Du Callbacks in einer Schleife registrieren willst, musst Du die Aktivitäten jedes Schleifendurchlaufs in eine Funktion packen und alles, was pro Durchlauf individuell sein muss, als Parameter übergeben bzw. als lokale Variable der Funktion deklarieren, damit der Datenstand, der für diese Schleife gebraucht wird, in einer Closure gekapselt ist.

    for (var i=0; i<100; i++) {
       collectValues(i);
    }
    
    function collectValues(i) {
       // Diese Variable liegt in der Closure der Callbacks und ist daher für jedes 
       // i einzigartig. Auch i liegt - als Parameter der Funktion - in der Closure
       var connection;
    
       pool.getConnection()
       .then(function(conn) {
          connection = conn;
          return connection.query("...", param1);
       })
       .then(function(rows) {
          console.log(i + "-1. "+rows[0].feldtyp);
          return connection.query("...", param2);
       })
       .then(...)
       .then(...)
       .then(function(rows) {
          console.log(i + "-4. "+rows[0].feldtyp);
          connection.release();
       });
    }
    

    Wenn Du es so machst, würde ich vermuten, dass Du das erwartete Durcheinander der Ergebnisse verschiedener Durchläufe in der Ausgabe bekommst.

    Vom Experiment mal weggeguckt: Für den gezeigten Anwendungsfall wäre es wohl einfacher, die Query anders aufzusetzen, so dass alle benötigten Werte in einem Ergebnis geliefert werden (z.B. SELECT idnr, feldtyp FROM werte WHERE idnr in (?, ?, ?, ?) ), dann bekommst Du 4 Rows mit idnr und Feldtyp. Dann hast Du weniger Promises, weniger Serialisierungsaufwand und auch weniger Anfragen an den SQL Server.

    Noch was anderes. Du KÖNNTEST die 4 Queries parallel starten, dann bekommst Du von jedem query-Aufruf ein eigenes Promise, und über Promise.all kannst Du ein Sammel-Promise erzeugen, das auf alle Ergebnisse wartet.

    pool.getConnection()
    .then(function(conn) {
       // Erzeuge Array (iterable-Objekt) mit 5 Einträgen. Die Connection auf Position 0,
       // die Promises für die Query-Ergebnisse auf den Positionen 1-4.
       var queries = [
          conn,
          conn.query("SELECT ...", param1),
          conn.query("SELECT ...", param2),
          conn.query("SELECT ...", param3),
          conn.query("SELECT ...", param4)
       ];
       // Erzeuge ein Sammelpromise, das auf alle 4 Queries wartet.
       return Promise.all(queries);
    })
    .then(function(results) {
       results[0].release();    // Connection freigeben
       console.log("1. " + results[1][0].feldtyp);
       console.log("2. " + results[2][0].feldtyp);
       console.log("3. " + results[3][0].feldtyp);
       console.log("4. " + results[4][0].feldtyp);
       
    })
    

    Das Interessante ist, dass der Parameter von Promise.all nicht nur aus Promises bestehen muss. Es sind auch non-Promise Werte erlaubt, die werden dann 1:1 durchgereicht. Auf diese Weise kannst Du das conn-Objekt durchreichen und brauchst keine Variable dafür.

    Alle JavaScript-Beispiele ungetestet und auf eigene Gefahr!

    Rolf

    --
    sumpsi - posui - clusi
    1. Hallo Rolf,

      erstmal vielen Dank für Deine ausführlichen Erläuterungen. Da werde ich sicher erstmal durcharbeiten müssen. Da für mich asynchrone Aufrufe noch völliges Neuland sind, habe ich so meine Verständnisprobleme und hätte erstmal keinen erwarteten Unterschied zwischen meiner und Deiner Schleife gesehen.

      Das von mir gezeigte Script ist natürlich eine Abstraktion meines eigentlichen Scripts und stellt keine 4 einfachen Selects dar. Ich hatte dieses Test-Script genutzt, um sicherzustellen, dass die SQL-Anfragen definitiv nacheinander gestellt werden, da mein eigentliches Script so aufgebaut ist. In meinem Fall wird zunächst geprüft, ob ein Eintrag vorhanden ist. Wenn ja, erfolgt ein Update, wenn nein, dann ein Insert. Meine Anfragen sind somit voneinander abhängig.

      Um etwas genauer zu werden, habe ich eine Websocket-Verbindung für einen Chat aufgebaut, in der der Client Kommandos schicken kann, die dann ausgewertet werden. Sowas wie, Abfragen aber auch Schreiben von Kundenrabatten. Bei der Anmeldung wird geprüft, ob es den Benutzer gibt und ob er die Berechtigung hat. Wenn ein Kommando eingegeben wird, wird ebenso geprüft, gibt es das Kommando, sind die Parameter (Kundennr., Kundenrabatt, etc.) plausibel. Kurz, es lassen sich Werte lesen und ändern. Und das eben MySQL-gestützt. Und hier ist ein Phänomen/Problem aufgetreten: Es kommt (scheinbar sporadisch, zumindest für) vor, dass im folgenden Script-Schnipsel die Consolen-Ausgaben erfolgen, aber das komplette MySQL-Statement scheinbar ignoriert wird (weil keine Ausgabe erfolgt):

      					console.log("vorher");
      					strQuery = 'update clients set status = "online", last_contact = "'+localISOTime+'", socketname = "'+socket.name+'" where client = "'+ident+'"';
      					pool.getConnection(function(err, connection) {
      						connection.query(strQuery, function(err, rows) {
      							if (err) {
      								console.log("Fehler bei: "+strQuery);
      								console.log(err);
      							} else {
      								if (rows.changedRows == 1) {
      									console.log("Client logged in");
      								} else {
      									console.log('Client unknown!');
      								}
      							}
      						});
      						connection.release();
      					});
      					console.log("nachher");
      

      Mal ganz platt gefragt: Muss ich die SQL-Abfragen einfach "kapseln" und über ein Callback aufrufen?

      LG Klaus

      1. Hallo Klaus1,

        in MySQL gibt es das Feature, dem INSERT Befehl die Klausel ON DUPLICATE KEY UPDATE hinzuzufügen. Vielleicht umgeht das das Problem, dass du eine serialisierte Ausführung brauchst. Guck's Dir mal im Handbuch an.

        Das konkrete Problem in deinem Snippet habe ich noch nicht verstanden. Welche Ausgabe GENAU passiert und welche nicht?

        Eins kann auf jeden Fall ein Problem werden: du baust das SQL Statement vor getConnection auf und speicherst es in einer mutmaßlich globalen Variablen. Wenn Du die nach dem getConnection-Aufruf noch anderweit benutzt, findet der Callback nicht mehr das vor, was du erwartest.

        Mir fällt auch auf, dass du den err Pfad von getConnection nicht ausgibst.

        Also ja, kapseln. Setze den Aufbau des SQL und den getConnection Aufruf in eine function und deklariere darin alle Variablen (mit var oder let), die von den Callbacks gebraucht werden. Die Daten, die als Grundlage für die Query dienen, übergibst du als Parameter. Den Pool kannst du global lassen, den wird es wohl nur einmal geben. Statt direkter Callbacks kannst du natürlich auch die Promises von promise-mysql nutzen.

        Auf diese Weise erhältst Du eine Closure mit den relevanten Daten für diesen SQL Zugriff.

        Mit den Promises hast du die weitere Möglichkeit, das vom letzten .then zurückgegebene Promise zurückzugeben und so die Kette nach der Rückkehr aus deiner function noch zu verlängern.

        Rolf

        --
        sumpsi - posui - clusi
        1. Puh, das klingt für mich ganz schön kompliziert.

          Um Deine Frage zu beantworten, was genau passiert bzw.nicht passiert: Es wird in der Console lediglich "vorher" und "nachher" ausgegeben, aber nicht die Consolen-Ausgaben innerhalb der SQL-Abfrage. Weder Erfolg noch Misserfolg noch Fehler.

          Mein Problem ist, dass ich einige Abfragen von einander abhängig hintereinander aufrufen muss:

          - ist der client bekannt?
          - wenn ja, ist das Kommando bekannt? wenn nein, insert into protokoll
          - wenn ja, gibt es den Parameter-Eintrag? wenn nein (und wert ungleich 0): insert
          - wenn ja, ist der neue Wert ungleich 0? wenn 0, dann delete
          - wenn ja, Update
          

          Einzelne oder gruppierbare Abfragen kann ich in einen Funktionsaufruf kapseln, aber wie mache ich das bei abhängigen Abfragen, die ich mit dem then-Händler serialisieren muss? Wobei auch bei ersterem für mich noch die Frage besteht, ob ich nur den SQL-Aufruf in die Funktion legen kann, aber die weitere Verarbeitung mit dem Ergebnis nicht.

          Vielleicht so?:

          strQuery = "select umsatz from umsaetze where kdnr = 123456";
          result = performqueries(strQuery);
          umsatz = result[0].umsatz;
          
          function performqueries(strQuery) {
          		pool.getConnection(function(err, connection) {
          			connection.query(strQuery, function(err, rows) {
          				if (err) {
          					console.log("Fehler bei: "+strQuery);
          					console.log(err);
          				} else {
          					return rows;
          				}
          			});
          			connection.release();
          		});
          }
          
          1. Tach!

            Einzelne oder gruppierbare Abfragen kann ich in einen Funktionsaufruf kapseln, aber wie mache ich das bei abhängigen Abfragen, die ich mit dem then-Händler serialisieren muss?

            Ob Code unmittelbar dasteht oder in einer Funktion steckt und nur an der Stelle aufgerufen wird, bleibt sich gleich. Lediglich die Scopes für verwendete Variablen müssen beachtet werden.

            Wobei auch bei ersterem für mich noch die Frage besteht, ob ich nur den SQL-Aufruf in die Funktion legen kann, aber die weitere Verarbeitung mit dem Ergebnis nicht.

            Wenn die Weiterverarbeitung abhängig vom Ergebnis ist, muss das zeitlich aufeinanderfolgen. Du kannst das zwar örtlich trennen, aber wie stellst du dann sicher, dass das eine fertig ist, bevor das andere weiterläuft?

            Vielleicht so?:

            strQuery = "select umsatz from umsaetze where kdnr = 123456";
            result = performqueries(strQuery);
            umsatz = result[0].umsatz;
            
            function performqueries(strQuery) {
            		pool.getConnection(function(err, connection) {
            			connection.query(strQuery, function(err, rows) {
            				if (err) {
            					console.log("Fehler bei: "+strQuery);
            					console.log(err);
            				} else {
            					return rows;
            				}
            			});
            			connection.release();
            		});
            }
            

            Nein. performqueries() gibt kein Ergebnis zurück. Das was im Callback passiert, wird an den aufrufenden Kontext zurückgegeben, nicht aber an den Kontext, in dem der Callback definiert wurde. performqueries() kann maximal ein Promise zurückgeben, das irgendwann später erfüllt wird. Auf dieses Promise kannst du mit angehängtem then() reagieren. Aber nicht mit unmittelbar folgendem Code. Du kannst nicht jemanden Brötchen holen schicken und direkt nach der Aufforderung mit dem Frühstück beginnen, wenn du dafür die Brötchen brauchst. Du kannst nur davon unabhängige Dinge erledigen. Zum Beispiel schon mal Tee kochen. In dem Fall musst du aber beachten, dass beides fertig ist, bevor du fortfährst, Tee ist fertig und die Brötchen sind eingetroffen. Der Tee ist in dem Beispiel nicht wirklich unabhängig. Das kann zwar unabhängig gestartet werden, aber die Ergebnisse müssen miteinander synchronisiert werden. Blumen gießen wäre hingegen komplett unabhängig.

            dedlfix.

            1. Ob Code unmittelbar dasteht oder in einer Funktion steckt und nur an der Stelle aufgerufen wird, bleibt sich gleich. Lediglich die Scopes für verwendete Variablen müssen beachtet werden.

              Das stimmt (leider) so nicht ganz, wie ein einfacher Test beweist:

              Das Script hier:

              for (i=1;i<=5 ;i++ ) {
              	console.log(i+". Durchlauf");
              	pool.getConnection()
              	.then(function(conn){
              		connection = conn;
              		var result = connection.query('select feldtyp from werte where idnr = ?', param1);
              		return result;
              	}).then(function(rows){
              		console.log(i+". Durchlauf");
              		console.log("1. "+rows[0].feldtyp);
              		var result = connection.query('select feldtyp from werte where idnr = ?', param2);
              		return result;
              	}).then(function(rows){
              		console.log("2. "+rows[0].feldtyp);
              		var result = connection.query('select feldtyp from werte where idnr = ?', param3);
              		return result;
              	}).then(function(rows){
              		console.log("3. "+rows[0].feldtyp);
              		var result = connection.query('select feldtyp from werte where idnr = ?', param4);
              		return result;
              	}).then(function(rows){
              		console.log("4. "+rows[0].feldtyp);
              		connection.release();
              		return;
              	}).catch(function(error){
              		if (connection && connection.release) connection.release();
              		//logs out the error
              		console.log(error);
              	});
              }
              

              erzeugt als Ausgabe:

              start
              1. Durchlauf
              2. Durchlauf
              3. Durchlauf
              4. Durchlauf
              fertig
              1. datum
              2. zeit
              3. text
              4. farbe
              1. datum
              2. zeit
              3. text
              4. farbe
              1. datum
              2. zeit
              3. text
              4. farbe
              1. datum
              2. zeit
              3. text
              4. farbe
              

              Wobei das Script hier:

              for (i=1;i<=5 ;i++ ) {
              	blabla(i);
              }
              
              function blabla(i) {	
              	pool.getConnection()
              	.then(function(conn){
              		connection = conn;
              		var result = connection.query('select feldtyp from werte where idnr = ?', param1);
              		return result;
              	}).then(function(rows){
              		console.log(i+". Durchlauf");
              		console.log("1. "+rows[0].feldtyp);
              		var result = connection.query('select feldtyp from werte where idnr = ?', param2);
              		return result;
              	}).then(function(rows){
              		console.log("2. "+rows[0].feldtyp);
              		var result = connection.query('select feldtyp from werte where idnr = ?', param3);
              		return result;
              	}).then(function(rows){
              		console.log("3. "+rows[0].feldtyp);
              		var result = connection.query('select feldtyp from werte where idnr = ?', param4);
              		return result;
              	}).then(function(rows){
              		console.log("4. "+rows[0].feldtyp);
              		connection.release();
              		return;
              	}).catch(function(error){
              		if (connection && connection.release) connection.release();
              		//logs out the error
              		console.log(error);
              	});
              }
              

              diese Ausgabe erzeugt:

              start
              fertig
              1. Durchlauf
              1. datum
              2. zeit
              3. text
              4. farbe
              2. Durchlauf
              1. datum
              2. zeit
              3. text
              4. farbe
              3. Durchlauf
              1. datum
              2. zeit
              3. text
              4. farbe
              4. Durchlauf
              1. datum
              2. zeit
              3. text
              4. farbe
              5. Durchlauf
              1. datum
              2. zeit
              3. text
              4. farbe
              

              Wobei nach meinem (und Deinem) bisherigen Verständnis ja kein Unterscheid sein sollte.

              1. Tach!

                Ob Code unmittelbar dasteht oder in einer Funktion steckt und nur an der Stelle aufgerufen wird, bleibt sich gleich. Lediglich die Scopes für verwendete Variablen müssen beachtet werden.

                Das stimmt (leider) so nicht ganz, wie ein einfacher Test beweist:

                Doch, das stimmt soweit. Du musst aber genau be(ob)achten, was ein bestimmer Code macht

                einPromise 
                  .then(function () { ... });
                

                Die Function an dieser Stelle ist nur definiert, da ist (noch) kein Aufruf. Es bleibt sich gleich zu

                function giveFunction() {
                  return function () { ... };
                }
                
                einPromise 
                  .then(giveFunction());
                

                Man kann die Function, die da als Parameter von then() übergeben werden soll, also direkt notieren oder über einen Funktionsaufruf erzeugen lassen. Setz bitte nicht die Übergabe einer Funktion mit deren späterem Aufruf gleich.

                Das Script hier:

                Da passt der Code nicht zur Ausgabe. Vor jedem "1. +feldtyp" muss ein "x. Durchlauf" stehen, denn da ist ein entsprechendes console.log() unmittelbar davor.

                dedlfix.

      2. Tach!

        					pool.getConnection(function(err, connection) {
        						connection.query(strQuery, function(err, rows) {
        							// ...
        						});
        						connection.release();
        					});
        					console.log("nachher");
        

        Ich habe mit promise-mysql keine Erfahrung. Aber wenn ich die Dokumentation richtig lese, kann/soll man dem query() als zweitem Parameter nur Werte für die Abfrage mitgeben. Die Dokumentation ist da nicht besonders umfangreich.

        Wenn du auf das Fertigstellen der Abfragen reagieren möchtest, wäre da ein .then() anzuhängen (und .catch() für eine Fehlerbehandlung). Andererseits, wenn ich den Code richtig lese, reicht promise-mysql den Aufruf nur an das zugrundeliegende mysqljs/mysql durch, und da übergibt man den Callback direkt als Parameter und nicht im Promise-Stil. Ob das wirklich so geht, kann ich aber nicht mit Bestimmtheit sagen. Es kann sein, dass genau das dein Problem ist, wenn der Callback lediglich als Parameter angesehen wird, für die Query ignoriert wird, weil nicht benötigt, und dann logischerweise kein Aufruf erfolgt.

        Der Vorteil von Promises ist größtenteils optischer Natur. Man kann damit Code untereinander schreiben, der zeitlich nacheinander abläuft, was bei mehreren aufeinander folgenden Handlungen übersichtlicher wird. Mit herkömmlichen Callbacks schachtelt man hingegen den nachfolgenden Code in den Callback und erzeugt damit eine immer tiefergehende Schachtelung im Code.

        doSomething(() => theFirst(params))
        .then(() => theSecond(params))
        .then(() => theThird(params))
        .then(() => theFourth(params));
        
        doSomething(() => theFirst(params, () => {
          theSecond(params, () => {
            theThird(params, () => {
              theFourth(params)
            })
          })
        });
        

        Wenn ich den Promise-Zusatz nähme, würde ich auch nach dessen Philosophie arbeiten und das zugrundeliegende nur im Notfall verwenden. Wenn ich mir aber das folgende Beispiel aus der Dokumentation von promise-mysql anschaue, dann komme ich ins Grübeln, was die da veranstalten.

        var mysql = require('promise-mysql');
        
        mysql.createConnection({
            host: 'localhost',
            user: 'sauron',
            password: 'theonetruering',
            database: 'mordor'
        }).then(function(conn){
            var result = conn.query('select `name` from hobbits');
            conn.end();
            return result;
        }).then(function(rows){
            // Logs out a list of hobbits
            console.log(rows);
        });
        

        Die Connection wird asynchron geholt und steht im Callback (sprich: then()) zur Verfügung. Soweit so gut. Im zweiten Teil wird die Query ausgeführt und ihr Ergebnis als Promise zurückgegeben. Dieses Promis kann aber nur im bereits erfüllten Zustand zurückgegeben werden, weil unmittelbar danach die Connection geschlossen wird. Das läuft da also in Wirklichkeit synchron und die Promise-Nutzung ist da nur wegen der then()-Nutzbarkeit anstatt Callback-Verschachtelung.

        Wie auch immer. Entscheide dich lieber für diese oder die andere Vorgehensweise. Wenn promise-mysql, dann ignorier die Dokumentation von mysqljs/mysql. Oder nimm ausschließlich mysqljs/mysql. Beides zu mischen hilft dir vermutlich nicht besonders, wenn du generell noch unsicher auf dem Gebiet bist.

        dedlfix.

        1. Hallo dedlfix,

          Laut Doku wartet conn.end(), bis alle Queries erledigt sind und gibt die Connection dann erst frei. Führt man mehrere Queries in Folge aus, wäre es vermutlich sinnvoll, selbst zu warten. Das macht es auch einfacher, noch Queries zwischenzuschieben, weil man dann nicht aufpassen muss das .end() zu verschieben. Im Fall der Nutzung eines Connection-Pools verwendet man nicht .end sondern .release, da ist es so, dass die Beispiele den release-Aufruf im Callback der Query zeigen und NICHT erwähnen, dass release() bis zum Ende laufender Queries wartet. Man sollte das also so übernehmen.

          Eine korrekte, auf den Connection Pool gestützte Sequenzierung müsste wohl so aussehen (mal abgesehen davon, dass sich diese Aufgabe durch INSERT ON DUPLICATE KEY UPDATE besser lösen ließe - es geht mir um die Sequenzierung). Das Freigeben der Connection gehört in einen finally-Block.

          function storeStuff(keyThingy, fooValue, barValue) {
             var connection = null;
             var needInsert = false;
          
             return pool.getConnection()
                .then(function(conn) {
                   connection = conn;
                   return connection.query("SELECT thingy FROM stuff WHERE thingy = ?", [keyThingy]);
                })
                .then(function(rows) {
                   needInsert = (rows.length === 0);
                   if (needInsert)
                       return connection.query("INSERT INTO stuff (thingy, foo, bar) VALUES(?,?,?)",
                                               [ keyThingy, fooValue, barValue ]);
                   else
                       return connection.query("UPDATE stuff SET foo=?, bar=? WHERE thingy=?", 
                                               [ fooValue, barValue, keyThingy ]);
                })
                .then(function(result) {
                   return result.affectedRows;
                })
                .finally(function() {
                   if (connection)
                      connection.release();
                });
          }
          

          Den Handler des letzten .then habe ich so geschrieben, dass er die Anzahl der geänderten Zeilen zurückgibt. Dieser Wert kann im weiteren Verlauf der Promise-Kette verwendet werden. Ein .catch ist in storeStuff nicht drin, ein Error wird durchgereicht.

          .finally ist so etwas ähnliches wie then, es wird aber im Erfolgs- UND im Fehlerfall aufgerufen. Hinzu kommt die Besonderheit, dass die finally-Funktion das letzte Result der then-Kette weder bekommt noch ändern kann. Das ist hier ganz praktisch, sonst müsste man dafür sorgen, das Ergebnis der thens durchzuschleifen. Aber auch .finally gibt ein Promise zurück, man kann sich also hinten anhängen und die Ergebnisse weiter verarbeiten.

          Genau dieses Promise gebe ich aus der storeStuff-Funktion zurück, d.h. man kann die Promise-Technik verwenden um das Ergebnis der darin ablaufenden Aktivitäten per Promise weiterzuverarbeiten. Wenn eine Query fehlschlägt wird der then nicht ausgeführt.

          storeStuff(17, "fup", "bup")
          .then(function(affected) {
             console.log("Stuffed, " + affected + " records affected");
          })
          .catch(function(err) {
             console.log("storeStuff failed, " + err);
          });
          

          Der Vorteil von Promises ist größtenteils optischer Natur.

          Den „kleineren“ Teil habe ich hier mal präsentiert: Einfaches Verketten von Aktionen, sogar über Funktionsgrenzen hinweg. Mach das mal mit Callbacks, da musst Du das in jedem Callback speziell vorsehen.

          Rolf

          --
          sumpsi - posui - clusi