0

I have a node.js server-side code that does some basic db operations against mysql. I am using mysql2-promise as the library.

Function layout:

  • contacts_GET(contactid) => getContactDetails();
  • contacts_GET(email) => getContactId(email) => getContactDetails(contactid);

Function implementations:

  contacts_GET : function(args, context, cb){
    if (args.pocid !== undefined ){
      db.getContactDetails(args.pocid)
        .then((results)=>{
          var resp = {
            "contacts" : results
          };
          cb(null, resp);
        }).catch((reason)=>{
          cb(JSON.stringify(errorHandler.throwInternalServerError(reason)));
        });
    }else if (args.email !== undefined ) {
      db.getContactId(null, args.email)
        .then((pocid)=>{
          logger.debug("got back pocid=", pocid);
          return db.getContactDetails(pocid);
        }).then((results)=>{
          logger.debug("got back result from getContactDetails=", results);
          var resp = {
            "contacts" : results
          };
          cb(null, responseObj(resp,args));
        }).catch((reason)=>{
          cb(JSON.stringify(errorHandler.throwInternalServerError(reason)));
        });
    }
  }


  getContactId : function(name, email, conn){
    logger.debug("Inside getContactId. [name, email]", name, email);
    if (conn === undefined ){
      return this.mysql.createConnection(this.options)
        .then((conn2)=>{
          const b = this.getContactId(name, email, conn2);
          conn2.end();
          return b;
        });
    }else{
      return conn.execute(`select pocid from Contacts where email = '${email}'`)
        .then(([rows, fields])=>{
          logger.debug("Inside getContactId. Result:", rows, fields);
          if (rows.length == 0) {
            return null;
          }else{
            return rows[0].pocid;            
          }
        });
    }
  }

  getContactDetails : function(pocid, conn){
    logger.debug("Inside getContactDetails. [pocid, defined conn]", pocid, (conn!==undefined));
    if (conn === undefined ){
      return this.mysql.createConnection(this.options)
        .then((conn2)=>{
          const b = this.getContactDetails(pocid, conn2);
          conn2.end();
          return b;
        });
    }else{
      const a = conn.execute("select name, email from Contacts where pocid=?", pocid)
        .then(([rows, fields])=>{
          logger.debug("Inside getContactDetails->cb. Result: ", rows);
          if (rows.length == 0) {
            return null;
          }else{
            return rows[0];            
          }
        });
      return a;
    }
  }

Per my understanding, that a Promise returned in a then can be used to chain down, I have composed the chain of promises for the two cases.

  1. When a contactid (pocid) is passed in, only the db.getContactDetails is called. Within this function, it creates a connection (which returns a promise), then executes the query (this returns a promise too), and the result is parsed and resolved. This usecase works.

  2. When an email (email) is passed in, the handler (contacts_GET) creates a chain of promises, starting with getContactId (which returns a promise chaining the connection creation, query execution and result parsing), chained to getContactDetails (same as above). This usecase fails.

My log statements indicate that in the second case, the execution abruptly stops after returning the promise from conn.execute in the getContactDetails function. It appears as if the Promise is never executed, hence the .then part (post execution of the query) never gets called. There are no errors or warnings about unhandled rejections. The control at that point just disappears.

I am struggling to understand why the same function works in the first usecase but fails in the second usecase. I have gone over the Promise documentation and read through every blog about boundary scenarios. I am still at a loss about this and would appreciate all help or pointers.

Thanks!

sujitv
  • 135
  • 1
  • 12
  • to clarify, you see `Inside getContactDetails` twice (once with `false` and second time with `true` at the end of that) ... and then you never see `Inside getContactDetails->cb` ? – Jaromanda X Oct 13 '17 at 11:34
  • I just figured out that the problem was not with the Promise code, but with how I was handling the errors in the catch. They were getting gobbled, so I was not seeing the sql error being thrown that was failing in the second query execution. – sujitv Oct 13 '17 at 15:30
  • As you can see from my code above, I had to switch to using formed string query when passing in the email, as using the parametrized form would not work (the query would return no values). I assume this was due to the escaping of the email address. Now I am running into the same problem with the other query too. Here, the value returned from the first lookup (contactid) is used in the second lookup and the parametrized query is throwing an SQL invalid parameter error. It works with a formed string query. – sujitv Oct 13 '17 at 15:33
  • Consider (a) purging the callback from `contacts_GET()` and returning a promise, (b) using [the disposer pattern described here](https://stackoverflow.com/a/28915678/3478010). With (a) and (b) your code should reduce to "at a glance" simpliciy - maybe 4 or 5 lines each in `contacts_GET()`, `getContactId()` and `getContactDetails()`. – Roamer-1888 Oct 13 '17 at 17:24
  • @Roamer-1888 - as to removing the callback from contacts_GET, that unfortunately is part of the interface I am coding against, so I have to return my response into the callback. I assume that is what you meant to have me remove. as to the disposer pattern, yes I too realized that I might be leaking connections when it starts working. I have now implemented a primitive disposer pattern similar to what you linked to. – sujitv Oct 13 '17 at 19:31
  • Yup that's exactly what I meant. Something that might be useful would be to make available a promise-returning adaptor method, say `contacts_GET_async()`, whilst retaining your current `contacts_GET()`. Programmers writing the consumer code could then be free to call either method, and chances are, in time, the promsiified version would gain popularity. – Roamer-1888 Oct 13 '17 at 20:30
  • Good to hear you have implemented a disposer. Just make sure it disposes under success and error conditions and you can't go far wrong. – Roamer-1888 Oct 13 '17 at 20:31

1 Answers1

0

Closing this question. I figured out that the issue was not with my Promise code, but with how I was processing the reason caught in the Promise.catch. This was resulting in an underlying sql error being ignored, leading to the behavior I noticed.

I also discovered, that node-mysql2 package treats query and execute methods quite differently. With the query method, the parameters are properly escaped and substituted; whereas with the execute method, parameter substitution is not carried out.

sujitv
  • 135
  • 1
  • 12
  • the whole reason for `execute()` is to allow separation between query and data so that parameter substitution is performed by server – Andrey Sidorov Oct 18 '17 at 06:39
  • i see. so if i understand you correctly, the parameter substitution for query is done by node-mysql, whereas the parameter substituion for execute is done by the mysql server. hmmm, i wonder why my queries were failing in execute then. something for me to dig in. – sujitv Oct 19 '17 at 15:06
  • yes, and client side substitution is simpler: when done by driver it's "escape all dangerous symbols + interpolate", when on server it's "parse sql + check syntax is valid + prepare execution plan + ... + use supplied data" – Andrey Sidorov Oct 20 '17 at 06:21