2

I'm trying to create a user account creation script with a focus on unique usernames - a prefix and a suffix from a pool, a list of existing usernames, and a list of reserved usernames.

That's just the start of it (no saving yet!), and already that would require three connections, so I just decided to see if I can code a function that would handle them all.

Here's my code so far - and it's on AWS Lambda, and tested via API Gateway, if that means anything:

const dbConnMysql = require('./dbController');
var methods = {
    createUser: function() {
        let getPrefixSuffixList = new Promise((resolve, reject) => {
            let connection = dbConnMysql.createConnection();
            dbConnMysql.startConnection(connection)
            .then((fulfilled) => {
                let table = 'userNamePool';
                return dbConnMysql.selectFrom(connection, table, '*', null);
            })
            .then((fulfilled) => {
                console.log(fulfilled);
                return dbConnMysql.closeConnection(connection)
                .then((fulfilled) => {
                    resolve(fulfilled);
                });
            })
            .catch((error) => {
                console.log(error);
                reject(error);
            });
        });

        let getTempUserNameList = new Promise((resolve, reject) => {
            // Same as getPrefixSuffixList, different table
        });

        let getRealUserNameList = new Promise((resolve, reject) => {
            // Same as getPrefixSuffixList, different table
        });

        return new Promise((resolve, reject) => {
            Promise.all([getPrefixSuffixList, getTempUserNameList, getRealUserNameList])
            .then((fulfilled) => {
                console.log(fulfilled[0]);
                console.log(fulfilled[1]);
                console.log(fulfilled[2]);
                let response = {
                    "statusCode": 200,
                    "headers": {"my_header": "my_value"},
                    "body": {"Prefix Suffix":fulfilled[0], "Temp UserName List":fulfilled[1], "Real UserName List":fulfilled[2]},
                    "isBase64Encoded": false
                };
                resolve(response);
            })
            .catch((error) => {
                let response = {
                    "statusCode": 404,
                    "headers": {"my_header": "my_value"},
                    "body": JSON.stringify(error),
                    "isBase64Encoded": false
                };
                reject(response);
            })
        });
    }
};    
module.exports = methods;

This function is called elsewhere, from index.js:

app.get('/createUserName', function (req, res) {
    var prom = Register.createUser();
    prom.then((message) => {
        res.status(201).json(message);
    })
    .catch((message) => {
        res.status(400).json(message);
    });
})

Now I'm not entirely sure if what I did with the Promise.All is correct, but from what little I know, if one promise fails, the Promise.All fails.

However, the individual promises do work just fine, and log out the respective results from the database. But inside the Promise.All, it all just logs out undefined.

Is there something I'm missing?

zack_falcon
  • 4,186
  • 20
  • 62
  • 108
  • 1
    What does `dbConnMysql.closeConnection(connection)` resolve to? Log your `fulfilled` there. If the Promise resolves, but doesn't resolve to a value, then what you see will be the result. Also, you're falling into the explicit Promise construction antipattern here. – CertainPerformance Aug 31 '18 at 09:21
  • 1
    `Promise.All is correct` don't forget to return it. -> `return Promise.all(` Also like @CertainPerformance says, get rid of the `new Promise`.. – Keith Aug 31 '18 at 09:27
  • @CertainPerformance it returns undefined! I see, so there's an issue with my layering I guess, or passing values from the outer fulfilled to the Promise.All. How can I fix this, or what is the antipattern? I will need to close the connection, before I proceed with anything else. – zack_falcon Aug 31 '18 at 09:33
  • See https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it . Basically, always `return` Promises and Promise chains instead of `new Promise`, whenever possible. – CertainPerformance Aug 31 '18 at 09:34
  • The anti-pattern is that you don't need the `new Promise`,.. just return the promise that's already there. -> `return dbConnMysql.startConnection(connection)` as well as of course `return Promise.all` too. – Keith Aug 31 '18 at 09:34

1 Answers1

2

The cause of your problem is this. You need to run the functions, these then return the promise that will eventually resolve:

    Promise.all([getPrefixSuffixList(), getTempUserNameList(), getRealUserNameList()])

Here is some simpler code as well. In general there is no need for new Promise(). This code may fix other issues. Also, the undefined could be being printed from any part of the code, make sure it's being printed where you think it is.

// Dummy MySQL connector
const dbConnMysql = {
  createConnection: () => 'Connection',
  startConnection: conn => new Promise(resolve => setTimeout(resolve, 100)),
  selectFrom: (conn, t, q, n) =>
    new Promise(resolve =>
      setTimeout(() => {
        console.log(`${conn}: SELECT ${q} FROM ${t}`);
        resolve(`x ${t} RECORDS`);
      }, 100)
    ),
  closeConnection: conn => new Promise(resolve => setTimeout(resolve, 100)),
};

const methods = {
  createUser() {
    const getPrefixSuffixList = () => {
      const connection = dbConnMysql.createConnection();
      return dbConnMysql
        .startConnection(connection)
        .then(() => {
          const table = 'userNamePool';
          return dbConnMysql.selectFrom(connection, table, '*', null);
        })
        .then(fulfilled => {
          console.log(fulfilled);
          return dbConnMysql.closeConnection(connection).then(() => fulfilled);
        })
        .catch(error => {
          console.log(error);
          // Note: this catch will stop the error from propagating
          // higher, it could also be the cause of your problem.
          // It's okay to catch, but if you want the error to
          // propagate further throw a new error here. Like this:
          throw new Error(error);
        });
    };

    const getTempUserNameList = () => {
      // Same as getPrefixSuffixList, different table
    };

    const getRealUserNameList = () => {
      // Same as getPrefixSuffixList, different table
    };

    return Promise.all([getPrefixSuffixList(), getTempUserNameList(), getRealUserNameList()])
      .then(fulfilled => {
        console.log('fulfilled[0]: ', fulfilled[0]);
        console.log('fulfilled[1]: ', fulfilled[1]);
        console.log('fulfilled[2]: ', fulfilled[2]);
        return {
          statusCode: 200,
          headers: { my_header: 'my_value' },
          body: {
            'Prefix Suffix': fulfilled[0],
            'Temp UserName List': fulfilled[1],
            'Real UserName List': fulfilled[2],
          },
          isBase64Encoded: false,
        };
      })
      .catch(error => ({
        statusCode: 404,
        headers: { my_header: 'my_value' },
        body: JSON.stringify(error),
        isBase64Encoded: false,
      }));
  },
};

methods.createUser();
psiphi75
  • 1,985
  • 1
  • 24
  • 36
  • Apologies for the delayed reply. I've finally been able to test this out. Unfortunately, it doesn't work. Without the outer promise on the Promise.All, the lambda function simply refuses to run, as app.get expects an async function. Also, before closing the connection, I inserted a logger to output the results of the select statement, which for some reason returns an `undefined`. – zack_falcon Sep 03 '18 at 06:59
  • @zack_falcon I didn't test my code, however, after writing the code and creating a basic wrapper for your MySQL functionality I found the error was in the `Promise.all([getPrefixSuffixList, getTempUserNameList, getRealUserNameList])` line, it should read `Promise.all([getPrefixSuffixList(), getTempUserNameList(), getRealUserNameList()])`. These function need to be run to return the promises. Try the new code, it should work ;) – psiphi75 Sep 04 '18 at 17:51