Search code examples
node.jsexpresspromisesynchronous

.then statements not executing sequentially


I have an application using Node.js/Express. Within this code I have the following promise designed to check if an email already exists in my (PostGres) database:

//queries.js

const checkEmail = function(mail) {

 return new Promise(function(resolve, reject) {

  pool.query('SELECT * FROM clients WHERE email = $1', [mail], function(error, results) {
   if (error) {
   reject(new Error('Client email NOT LOCATED in database!'));     
   } else {
   resolve(results.rows[0]);
   }
  })  //pool.query

 });  //new promise 
}

In my 'main (server.js)' script file, I have a route which is called upon submission of a 'signup' form. When the post to this route is processed...I run the script above to check if the passed email address is already located in the database, along with various other 'hashing' routines:

My code is as follows:

//server.js

const db = require('./queries');
const traffic = require('./traffic'); 
const shortid = require('shortid');

...

app.post('/_join_up', function(req, res) {

 if (!req.body) {
 console.log('ERROR: req.body has NOT been returned...');
 return res.sendStatus(400)
 }


var newHash, newName;
var client = req.body.client_email;
var creds = req.body.client_pword;
var newToken = shortid.generate();
var firstname = req.body.client_alias; 


 db.sanitation(client, creds, firstname).then(
  function(direction) {
    console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
  }
 ).then(
 db.checkEmail(client).then(
  function(foundUser) {
    console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!', foundUser);
  }, 
  function(error) {
    console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...');
  }
 )).then(
 traffic.hashPassword(creds).then(
  function(hashedPassword) {
    console.log('PASSWORD HASHED');
    newHash = hashedPassword;
  }, 
  function(error) {
    console.log('UNABLE TO HASH PASSWORD...' + error);
  }
 )).then(
 traffic.hashUsername(firstname).then(
  function(hashedName) {
    console.log('NAME HASHED');
    newName = hashedName;
  }, 
  function(error) {
    console.log('UNABLE TO HASH NAME...' + error);
  }
 )).then(
 db.createUser(client, newName, newHash, newToken).then(
  function(data) {
  console.log('REGISTERED A NEW CLIENT JOIN...!!!');
    
  res.redirect('/landing');  //route to 'landing' page...
  }, 
  function(error) {
    console.log('UNABLE TO CREATE NEW USER...' + error);
  }
 ))
 .catch(function(error) {
 console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING OF THE USER-SUPPLIED INFORMATION...' + error);
 res.redirect('/');  
 });

});  //POST '_join_up' is used to register NEW clients...

My issue is the '.then' statements do not appear to run sequentially. I was under the impression such commands only run one after the other...with each running only when the previous has completed. This is based upon the logs which show the readout of the 'console.log' statements:

USER-SUPPLIED DATA HAS PASSED INSPECTION
PASSWORD HASHED
NAME HASHED
UNABLE TO CREATE NEW USER...Error: Unable to create new CLIENT JOIN!
USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...

As mentioned previously, I am under the impression the '.then' statements should run synchronously, therefore the last statement ("USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...") should in fact be after the first...before the "PASSWORD HASHED" according to the layout of the '.then' statements. Is this normal behavior...or do I have an error in my code?

Sorry for my confusion however I find '.then' statements and promises to be somewhat confusing for some reason. I thank you in advance.


Solution

  • TLDR - You must pass a function reference to .then() so the promise infrastructure can call that function later. You are not doing that in several places in your code.

    A more specific example from your code:

    You have several structures like this:

    .then(db.createUser().then())
    

    This is incorrect. This tells the interpreter to run db.createUser() immediately and pass its return result (a promise) to .then(). .then() will completely IGNORE anything you pass is that is not a function reference and your promises will not be properly chained.

    Instead, you must pass a function reference to .then() something like this (not sure what execution logic you actually want):

    .then(() => { return db.createUser.then()})
    

    Then main point here is that if you're going to sequence asynchronous operations, then you must chain their promises which means you must not execute the 2nd until the first calls the function you pass to .then(). You weren't passing a function to .then(), you were executing a function immediately and then passing a promise to .then(p) which was completely ignored by .then() and your function was executed before the parent promise resolved.


    FYI, sequencing a bunch of asynchronous operations (which it appears you are trying to do here) can take advantage of await instead of .then() and end up with much simpler looking code.