Search code examples
javascriptmysqlasynchronousecmascript-6models

ES6 Classes and synchronous execution


I am building a model class for an order data entity, in an API. At the moment this entity has 2 endpoints, one for retrieving summary data /orders/{id}/summary, and another for payment data /orders/{id}/payment. The summary works 100%, and the payment endpoint gets all data, except where there's a secondary DB query.

The code in my order.js model:

'use strict';

/* Include MySQL connection */
var dbConnection = require(appDir + process.env.PATH_LIB + 'database');

/* Include PaymentMethod model */
var PaymentMethod = require('./paymentmethod');


class Order {
  constructor (data, requestTemplate) {
    this.data = data;
    switch (requestTemplate) {
      case 'summary':
        this.data.platform = _getPlatform(data.TakeALot);
        this.data.source = _getSource(data.idKalahariOrder,data.idAffiliate,data.TakeALot);
        this.data.reference = _getExternalReference(data.idKalahariOrder,data.AffiliateReference);
        break;
      case 'payment':
        this.data.health = _getHealth(data.Auth);
        this.data.timeline = _getTimeline(data.Auth);
        break;
    };
  }

  getPayments () {
    var paymentList = [];
    var paymentMethods = [];

    if (this.data.PaymentMethod) {
      paymentList.push(this.data.PaymentMethod);
    };
    if (this.data.PaymentMethods) {
      this.data.PaymentMethods.split(',').forEach(function(e){
        if (paymentList.indexOf(e) == -1) {
          paymentList.push(e);
        }
      })
    };
    for (var i = 0; i < paymentList.length; i++) {
      console.log(paymentList[i]);
      paymentMethods.push(new PaymentMethod(this.data.idOrder,paymentList[i]));
    };
    console.log(paymentMethods);
    this.data.payments = paymentMethods;
  }
}

/* Find a single ORDER SUMMARY data */
Order.findSummaryById = function (id, callback) {
  dbConnection(function(err, conn){
    conn.query('SELECT <<query text removed>> WHERE o.idOrder = ?', [id], function(err, rows) {
      if (err) return callback(err);
      callback(null, new Order(rows[0], 'summary'));
    });
    conn.release();
  })
};

/* Find a single ORDER PAYMENT data */
Order.findPaymentById = function (id, callback) {
  dbConnection(function(err, conn){
    conn.query('SELECT <<query text removed>> WHERE o.idOrder = ?', [id], function(err, rows) {
      if (err) return callback(err);
      let order = new Order(rows[0], 'payment');
      order.getPayments();
      callback(null, order);
    });
    conn.release();
  })
};

/* Build order timeline */
function _getTimeline(status) {
  <<business logic removed>>
  return orderTimeline;
}

/* Determine order health */
 function _getHealth(status) {
  <<business logic removed>>
  return health;
}

/* Determine order source */
 function _getSource(idKalahariOrder, idAffiliate, TakeALot) {
  <<business logic removed>>
  return source;
}

/* Determine client platform */
function _getPlatform(clientId) {
  <<business logic removed>>
  return platform;
}

/* Determine external reference */
function _getExternalReference(idKalahariOrder, AffiliateReference) {
  <<business logic removed>>
  return reference;
}

module.exports = Order;

The code in my paymentmethod.js model:

'use strict';

/* Include MySQL connection */
var dbConnection = require(appDir + process.env.PATH_LIB + 'database');

class PaymentMethod {
  constructor(idOrder, paymentType) {
    var SQL = '';
    switch (paymentType) {
      case 'Credit Card':
        SQL = 'SELECT <<query text removed>> WHERE idOrder = ?';
        break;
      default:
        SQL = 'SELECT <<query text removed>> WHERE idOrder = ?';
    };
    dbConnection(function(err, conn){
      conn.query(SQL, [idOrder], function (err, data) {
        if (err) return callback(err);
        if (data) {
          this.paymentType = paymentType;
          this.paymentAmount = data.paymentAmount;
          this.paymentReference = data.paymentReference;
          this.paymentState = data.paymentState;
        }
      });
      conn.release();
    });
  }
}

module.exports = PaymentMethod;

The issue seems to be in the getPayments method on the Order class. When the push paymentMethods.push(new PaymentMethod(this.data.idOrder,paymentList[i])); is executed, it seems that due to async, the following lines of code are executed before the new class instance of PaymentMethod is pushed into the array. I have even tried to first assign the new class instance to a variable, and then push this, but it has the same affect. Any ideas?

EDITED:

I have changed the PaymentMethod class to:

class PaymentMethod {
  constructor(idOrder, paymentType) {
    var SQL = '';
    switch (paymentType) {
      case 'Credit Card':
        SQL = 'SELECT <<SQL text>> WHERE idOrder = ?';
        break;
      default:
        SQL = 'SELECT <<SQL text>> WHERE idOrder = ?';
    };
    return new Promise(function (resolve) {
      this.loadData(SQL, idOrder).then(function (data) {
        console.log(data);
        this.paymentType = paymentType;
        this.paymentAmount = data.paymentAmount;
        this.paymentReference = data.paymentReference;
        this.paymentState = data.paymentState;
        resolve (this);
      });
    });
  };

  loadData (SQL, idOrder) {
    console.log('fetching data ...');
    return new Promise (function (resolve) {
      dbConnection(function(err, conn){
        conn.query(SQL, [idOrder], function (err, data) {
          if (err) return callback(err);
          if (data) {
            resolve (data);
          }
        });
        conn.release();
      });
    })
  };
}

And, call it here:

  getPayments () {
    var paymentList = [];
    var paymentMethods = [];

    if (this.data.PaymentMethod) {
      paymentList.push(this.data.PaymentMethod);
    };
    if (this.data.PaymentMethods) {
      this.data.PaymentMethods.split(',').forEach(function(e){
        if (paymentList.indexOf(e) == -1) {
          paymentList.push(e);
        }
      })
    };
    for (var i = 0; i < paymentList.length; i++) {
      console.log(paymentList[i]);
      //var paymentDet = new PaymentMethod(this.data.idOrder,paymentList[i]);
      new PaymentMethod(this.data.idOrder,paymentList[i]).then(function(data) {
        console.log('detail:',data);
        paymentMethods.push(data);
      });
    };
    console.log(paymentMethods);
    this.data.payments = paymentMethods;
  }

But, still no luck ....

FINAL SOLUTION Order class:

class Order {
  constructor (data, requestTemplate) {
    this.data = data;
    switch (requestTemplate) {
      case 'summary':
        this.data.platform = _getPlatform(data.TakeALot);
        this.data.source = _getSource(data.idKalahariOrder,data.idAffiliate,data.TakeALot);
        this.data.reference = _getExternalReference(data.idKalahariOrder,data.AffiliateReference);
        break;
      case 'payment':
        this.data.health = _getHealth(data.Auth);
        this.data.timeline = _getTimeline(data.Auth);
        break;
    };
  }

  getPayments () {
    var self = this;
    return new Promise(function (resolve) {
      var paymentList = [];
      var paymentMethods = [];

      if (self.data.PaymentMethod) {
        paymentList.push(self.data.PaymentMethod);
      };
      if (self.data.PaymentMethods) {
        self.data.PaymentMethods.split(',').forEach(function(e){
          if (paymentList.indexOf(e) == -1) {
            paymentList.push(e);
          }
        })
      };
      for (var i = 0; i < paymentList.length; i++) {
        new PaymentMethod(self.data.idOrder,paymentList[i]).then(function(data) {
          paymentMethods.push(data);
          if (paymentMethods.length == paymentList.length) {
            self.data.payments = paymentMethods;
            resolve(self);
          }
        })
      };
    })
  }
}

PaymentMethod class:

class PaymentMethod {
  constructor(idOrder, paymentType) {
    var SQL = '';
    switch (paymentType) {
      case 'Credit Card':
        SQL = 'SELECT <<query text>> WHERE idOrder = ?';
        break;
      default:
        SQL = 'SELECT <<query text>> WHERE idOrder = ?';
    };

    var self = this;

    return new Promise(function (resolve) {
      self.loadData(SQL, idOrder).then(function (data) {
        self.paymentType = paymentType;
        self.paymentAmount = data.paymentAmount;
        self.paymentReference = data.paymentReference;
        self.paymentState = data.paymentState;
        resolve (self);
      }).catch(function(error) {
        console.log('Error occurred!', error);
      });
    }).catch(function(error) {
      console.log('Error occurred!', error);
    });
  }

  loadData (SQL, idOrder) {
    return new Promise (function (resolve) {
      dbConnection(function(err, conn){
        conn.query(SQL, [idOrder], function (err, data) {
          if (err) return callback(err);
          if (data) {
            resolve (data[0]);
          }
        });
        conn.release();
      });
    }).catch(function(error) {
      console.log('Error occurred!', error);
    });
  };
}

Code that instantiates a new Order instance:

Order.findPaymentById = function (id, callback) {
  dbConnection(function(err, conn){
    conn.query('SELECT <<query text>> WHERE o.idOrder = ?', [id], function(err, rows) {
      if (err) return callback(err);
      let order = new Order(rows[0], 'payment');
      order.getPayments().then(function(){
        callback(null, order);
      });
    });
    conn.release();
  })
};

Solution

  • In order to print the results and return the right data, I would advise returning a promise in the getPayments function - that way the callee function will have access to all the payment methods inside a .then closure.

    For example:

    Callee function:

    getPayments().then(function(paymethods) { 
      //use paymethods array here for something
      console.log(paymethods);
    });
    

    getPayments function:

    function getPayments () {
      var paymentList = [];
      var paymentMethods = [];
    
      if (this.data.PaymentMethod) {
        paymentList.push(this.data.PaymentMethod);
      };
      if (this.data.PaymentMethods) {
        this.data.PaymentMethods.split(',').forEach(function(e){
          if (paymentList.indexOf(e) == -1) {
            paymentList.push(e);
           }
        })
      };
      return new Promise(function(resolve) {
        var last = false;
        for (var i = 0; i < paymentList.length; i++) {
          if (i === paymentList.length-1) { last=true }
          console.log(paymentList[i]);
          new PaymentMethod(this.data.idOrder,paymentList[i]).then( function(data) {
            console.log('detail:',data);
            paymentMethods.push(data);
            if (last) {
              resolve(paymentMethods);
            }
          });
        };
      });
    }