calling javascript class method from another method from the same class - javascript

I'm trying to write a wrapper class called Mongo. When I call getCollection() inside insert(), but I'm getting 'TypeError: this.getCollection is not a function'.
const mongoClient = require('mongodb').MongoClient;
const connectionString = process.env.MONGODB_CONNECTION_STRING;
const mongoOptions = {
connectTimeoutMS: 500,
autoReconnect: true
};
function Mongo(dbName, collectionName) {
this.dbName = dbName;
this.collectionName = collectionName;
this.db = null;
this.collectionCache = {};
this.getDB = function () {
return new Promise(function (resolve, reject) {
if (this.db == null) {
mongoClient.connect(connectionString, mongoOptions, function (err, db) {
if (err) reject(err);
this.db = db.db(this.dbName);
resolve(this.db);
});
} else {
resolve(this.db);
}
});
};
this.getCollection = function () {
return new Promise(function (resolve, reject) {
if (this.collectionName in this.collectionCache) {
resolve(this.collectionCache[this.collectionName]);
} else {
getDB().then(function(db) {
db.collection(this.collectionName, function (err, collection) {
if (err) reject(err);
this.collectionCache[this.collectionName] = collection;
resolve(collection);
});
}, function (err) {
reject(err);
});
}
});
};
this.insert = function(docs) {
return new Promise(function (resolve, reject) {
this.getCollection().then(function(collection) {
collection.insert(docs, function(err, results) {
if (err) reject(err);
resolve(results);
});
});
}, function (err) {
reject(err);
});
}
}
module.exports = Mongo;
How this class is instantiated, and the insert method is called.
const assert = require('assert');
const Mongo = require('../data/mongo');
describe('MongoTest', function() {
it('TestInsert', function() {
var mongo = new Mongo('testdb', 'humans');
var testDoc = {
_id: 1100,
name: 'tommy',
tags: ['cool', 'interesting']
};
mongo.insert(testDoc).then(function(result){
assert.equal(result._id, 1100);
});
})
})

Where you're calling the getCollection function, this no longer refers to the Mongo object, it refers to the callback function you've passed as a parameter to the Promise.
There are many ways of fixing that. Here are a few:
Keeping a reference to the object:
this.insert = function(docs) {
var self = this;
return new Promise(function (resolve, reject) {
self.getCollection().then(function(collection) {
collection.insert(docs, function(err, results) {
if (err) reject(err);
resolve(results);
});
});
}, function (err) {
reject(err);
});
}
Binding the context:
this.insert = function(docs) {
var callbackFunction = function (resolve, reject) {
this.getCollection().then(function(collection) {
collection.insert(docs, function(err, results) {
if (err) reject(err);
resolve(results);
});
});
};
return new Promise(callbackFunction.bind(this), function (err) {
reject(err);
});
}

Update your insert function as follows -
this.insert = function(docs) {
var _self = this;
return new Promise(function (resolve, reject) {
_self.getCollection().then(function(collection) {
collection.insert(docs, function(err, results) {
if (err) reject(err);
resolve(results);
});
});
}, function (err) {
reject(err);
});
}
You are getting this error because you are calling getCollection inside callback function where this does not refer to Mongo class. So you need to store the Mongo class reference in a variable and then use it.

Related

mongodb nodejs store value outside connect

I need a way to store value outside mongoDB connect call:
read(object) {
let result
MongoClient.connect(this.url, function (err, db) {
if (err!=null){
result = err;
} else {
db.collection(object.collection).find(object.field).toArray(function(err, docs) {
assert.equal(err, null);
db.close();
result = docs;
});
}
});
return result
}
When i call this method, which is part of a class, return is called before result assignment, as normal.
Example: console.log(read(obj)) returns undefined
The idea is to store value in a variable and return might wait until connect terminate.
Is there any way to resolve this problem?
Without promise:
Call return inside find and err function:
read(object) {
let result
MongoClient.connect(this.url, function (err, db) {
if (err!=null){
result = err;
return result; //here
} else {
db.collection(object.collection).find(object.field).toArray(function(err, docs) {
assert.equal(err, null);
db.close();
result = docs;
return result; // here
});
}
});
}
Or set a timeout for return with enough time to wait for other process to end:
read(object) {
let result
MongoClient.connect(this.url, function (err, db) {
if (err!=null){
result = err;
} else {
db.collection(object.collection).find(object.field).toArray(function(err, docs) {
assert.equal(err, null);
db.close();
result = docs;
});
}
});
setTimeout(function(){ return result}, 3000); // 3secs
}
With Promise you can try the following:
function read(object) {
let result
return new Promise((resolve, reject) => {
MongoClient.connect(this.url, function (err, db) {
if (err!=null){
reject(err);
} else {
db.collection(object.collection).find(object.field).toArray(function(err, docs) {
db.close();
if (err) {
reject(err);
} else {
resolve(docs);
}
});
}
});
});
}
// then you can call the function and use the result like this
read(obj).then(docs => {
console.log(docs);
})
.catch(err => {
// handle error
console.log(err);
})

javascript mysql and promises

I'm working with promises, or at least trying to.
Here is what my code look's so far:
checkEmailUsername = function(mail, user) {
return new Promise(function(resolve, reject) {
var query;
query = "SELECT * FROM users WHERE email = ? OR username = ?";
return connection.query(query, [mail, user], function(err, row, fields) {
if (err) {
reject(err);
}
console.log(row.length);
if (row.length > 0) {
return resolve(true);
} else {
return resolve(false);
}
});
}).then()["catch"]();
};
Question is. How to return the resolve values from inside the promise?
You can't use return here, use the promise resolve function to return the rows from db to the caller.
checkEmailUsername = function (mail, user) {
return new Promise(function (resolve, reject) {
var query;
query = "SELECT * FROM users WHERE email = ? OR username = ?";
connection.query(query, [mail, user], function (err, row, fields) {
if (err) {
reject(err);
}
console.log(row.length);
resolve(row);
});
};
checkEmailUsername(mail, user).then(function (rows) {
// do something with the rows
}).catch((err) => {
//handle error
}
}

Why is promise ignoring .then when you have a Promise.all in the .then

Here are my promises
findBookings()
.then(Promise.all([findInvoiceSum, findReceipt]))
.then(sendResult)
.catch(err => {
console.log("getBookingList ERR: " + err);
res.json({error:true,err})
}
)
I would exspect it to run findBookings() and when its resolved THEN it will run the chain with ([findInvoiceSum, findReceipt]) and when both are finished THEN it will run sendResult.
Unfortunately this is what happens (from console log)
=====START findInvoiceSum=====
=====START findReceipt=====
=====START findBookings=====
=====RESOLVE findInvoiceSum=====
=====RESOLVE findReceipt=====
=====RESOLVE findBookings=====
=====sendResult=====
=====RESOLVE sendResult=====
Why is that? Why is the .then on my first promise being ignored?
Here is my controller:
//
// The FIRST promise that needs to be resolved
//
var findBookings = function() {
return new Promise((resolve, reject) => {
console.log("=====START findBookings=====")
bookingTable.aggregate([
...lots of code...
], function (err, data) {
if (!err) {
bookingArray = data;
console.log("=====RESOLVE findBookings=====")
resolve(data);
} else {
reject(new Error('findBooking ERROR : ' + err));
};
});
})};
//
// The findInvoiceSum chain promise
//
var findInvoiceSum = new Promise(
(resolve, reject) => {
console.log("=====START findInvoiceSum=====")
invoiceSumTable.aggregate([
...lots of code...
], function (err, data) {
if (!err) {
console.log("=====RESOLVE findInvoiceSum=====")
resolve(data);
} else {
reject(new Error('findExpense ERROR : ' + err));
};
});
});
//
// The findReceipt chain promise
//
var findReceipt = new Promise(
(resolve, reject) => {
console.log("=====START findReceipt=====")
receiptTable.aggregate([
...lots of code...
],function(err, data) {
if (!err) {
console.log("=====RESOLVE findReceipt=====")
resolve(data);
} else {
reject(new Error('ERR findPropert : ' + err));
};
});
});
//
// Send the result
//
var sendResult = function([invoiceArray, receiptArray]) {
return new Promise((resolve, reject) => {
console.log("=====sendResult=====")
res.json({error:false,
"booking":bookingArray,
"invoice":invoiceArray,
"receipt":receiptArray})
console.log("=====RESOLVE sendResult=====")
resolve();
});
};
//
// Run the promises
//
findBookings()
.then(Promise.all([findInvoiceSum, findReceipt]))
.then(sendResult)
.catch(err => {
console.log("getBookingList ERR: " + err);
res.json({error:true,err})
}
)
I would exspect it to run findBookings() and when its resolved THEN it
will run the chain with ([findInvoiceSum, findReceipt])
To accomplish this you'd need to pass a function in then
findBookings()
.then(() => Promise.all([findInvoiceSum(), findReceipt()]))
And make findInvoiceSum and findReceipt to be a functions as well.
function findReceipt() {
return new Promise(
(resolve, reject) => {
console.log("=====START findReceipt=====")
receiptTable.aggregate([
...lots of code...
],function(err, data) {
if (!err) {
console.log("=====RESOLVE findReceipt=====")
resolve(data);
} else {
reject(new Error('ERR findPropert : ' + err));
};
});
});
}
Since you haven't wrapped findInvoiceSum and findReceipt
into a function the promise is initialised itself,
You can wrap the promises into functions and use Promise.all as follows
var findBookings = function() {
return new Promise((resolve, reject) => {
console.log("=====START findBookings=====")
bookingTable.aggregate([
...lots of code...
], function(err, data) {
if (!err) {
bookingArray = data;
console.log("=====RESOLVE findBookings=====")
resolve(data);
} else {
reject(new Error('findBooking ERROR : ' + err));
};
});
})
};
//
// The findInvoiceSum chain promise
//
var findInvoiceSum = function() {
return new Promise(
(resolve, reject) => {
console.log("=====START findInvoiceSum=====")
invoiceSumTable.aggregate([
...lots of code...
], function(err, data) {
if (!err) {
console.log("=====RESOLVE findInvoiceSum=====")
resolve(data);
} else {
reject(new Error('findExpense ERROR : ' + err));
};
});
});
};
//
// The findReceipt chain promise
//
var findReceipt = function() {
return new Promise(
(resolve, reject) => {
console.log("=====START findReceipt=====")
receiptTable.aggregate([
...lots of code...
], function(err, data) {
if (!err) {
console.log("=====RESOLVE findReceipt=====")
resolve(data);
} else {
reject(new Error('ERR findPropert : ' + err));
};
});
});
};
//
// Send the result
//
var sendResult = function([invoiceArray, receiptArray]) {
return new Promise((resolve, reject) => {
console.log("=====sendResult=====")
res.json({
error: false,
"booking": bookingArray,
"invoice": invoiceArray,
"receipt": receiptArray
})
console.log("=====RESOLVE sendResult=====")
resolve();
});
};
// Find invoicesum and receipt
var invoiceAndReceipt = function() {
return Promise.all([findInvoiceSum(), findReceipt()]).then(function(data) {
Promise.resolve(data);
}).catch(function(error) {
Promise.reject(error);
});
}
//
// Run the promises
//
findBookings()
.then(invoiceAndReceipt)
.then(sendResult)
.catch(err => {
console.log("getBookingList ERR: " + err);
res.json({ error: true, err })
});

Using NodeJS Promises with MySQL

I'm trying to reverse engineer a Node script from a library (research project) to do the following tasks sequentially:
1) Open and read a file (e.g., 'input.txt'). For simplicity, assume that the contents are properly formatted SQL queries)
2) Create a connection to a MySQL database
3) Execute the queries (constructed from (1) -- assume queries are properly defined in the file)
4) Terminate connection with the database
I want these tasks to be executed in order (i.e., 1--4). I don't have much experience in using Promises (Bluebird). Here is an excerpt of the code I have so far:
//Read the input file
function readFilePromise(){
return new Promise(function(resolve, reject){
var filePath = path.join(__dirname, filename);
//asynchronous read
fs.readFile(filePath, 'utf8', function (err, text){
if (err)
reject(err);
else
resolve(text.split('\n'));
});
})
}
//create connection
function createConnectionPromise(){
return new Promise(function (resolve, reject){
var connection = mysql.createConnection(connectionOptions);//global
connection.connect(function(err){
if(err){
console.log('Error connecting to Db');
reject(err);
}
else{
console.log('Connection established');
resolve(connection);
}
});
})
}
//do transactions
function doTransactionsPromise (data){
return new Promise(function (resolve, reject){
var connection = data[0];
var lines = data[1];
var topPromises = [];
lines.forEach(function(sSQL){
var p = new Promise(function(resolve,reject){
console.log('Add: ' + sSQL);
makeTransaction(connection, sSQL);
return connection;
});
topPromises.push(p);
});
resolve(topPromises);
});
}
//make transaction
function makeTransaction(connection, sSQL){
connection.beginTransaction(function(err){
function treatErro(err, connection) {
console.log('Failed to insert data in the database . Undoing!');
connection.rollback();
}
function final() {
connection.commit(function(err) {
if(err) {
treatErro(err, connection);
}
else {
console.log('Added: ' + sSQL);
return connection;
}
});
}
if(err) {
treatErro(err, connection);
}
else {
connection.query(sSQL, function (err, result) {
if(err) {
console.log(sSQL);
treatErro(err, connection);
}
else {
id = result.insertId;
}
});
final();
}
});
}
Promise.all([createConnectionPromise(), readFilePromise()])
.then(doTransactionsPromise)
.then(function(promises){
Promise.all(promises)
.then(function(data){
var connection = data[0];
connection.end();
});
})
.catch(function(error) {
console.log('Error occurred!', error);
});
The queries are executed fine but the connection to the DB does not terminate. Any help is appreciated.
PS: I'm sure the code can be improved massively.
The possible problem I see in your code is in function doTransaction
function doTransactionsPromise (data){
return new Promise(function (resolve, reject){
var connection = data[0];
var lines = data[1];
var topPromises = [];
lines.forEach(function(sSQL){
var p = new Promise(function(resolve,reject){
console.log('Add: ' + sSQL);
makeTransaction(connection, sSQL);
return connection;
});
// P is never fullfilled.
//Either transfer the responsibility to full-fill the promise to makeTransaction
// or makeTransaction function should return the promise which is full-filled by itself.
topPromises.push(p);
});
resolve(topPromises);
});
}
I have not tested the code, but following code should do
//Read the input file
function readFilePromise(){
return new Promise(function(resolve, reject){
var filePath = path.join(__dirname, filename);
//asynchronous read
fs.readFile(filePath, 'utf8', function (err, text){
if (err)
reject(err);
else
resolve(text.split('\n'));
});
})
}
//create connection
function createConnectionPromise(){
return new Promise(function (resolve, reject){
var connection = mysql.createConnection(connectionOptions);//global
connection.connect(function(err){
if(err){
console.log('Error connecting to Db');
reject(err);
}
else{
console.log('Connection established');
resolve(connection);
}
});
})
}
//do transactions
function doTransactionsPromise (data){
var connection = data[0];
var lines = data[1];
var topPromises = [];
topPromise = lines.map(function(sSQL){
return makeTransaction(connection, sSQL);
});
return Promise.all(topPromises).then( function(){
return connection;
},function(){
return connection;
});
}
//make transaction
function makeTransaction(connection, sSQL){
return new Promise(resolve, reject, function(){
connection.beginTransaction(function(err){
function treatErro(err, connection) {
console.log('Failed to insert data in the database . Undoing!');
connection.rollback();
reject(connection);
}
function final() {
connection.commit(function(err) {
if(err) {
treatErro(err, connection);
}
else {
console.log('Added: ' + sSQL);
resolve(connection);
return connection;
}
});
}
if(err) {
treatErro(err, connection);
}
else {
connection.query(sSQL, function (err, result) {
if(err) {
console.log(sSQL);
treatErro(err, connection);
}
else {
id = result.insertId;
}
});
final();
}
});
})
}
Promise.all([createConnectionPromise(), readFilePromise()])
.then(doTransactionsPromise)
.then(function(connection){
return connection.end();
})
.catch(function(error) {
console.log('Error occurred!', error);
});

Unit testing a method that calls other methods

I am new to testing. I am trying to unit-test a method that calls 2 other methods. I tried stubbing those 2 methods, but it looks like the original methods are still getting called. I am trying to test whether my method adds the object the savePoll method returns to the user.polls array.
test.js
var UserStub = sinon.stub();
var PollStub = sinon.stub();
var OptionStub = sinon.stub();
var saveOptionsStub = sinon.stub();
var savePollStub = sinon.stub();
var users = proxyquire('../controllers/users', {
'../models/user': UserStub,
'../models/poll': PollStub,
'../models/option': OptionStub
});
beforeEach(function() {
res = {
json: sinon.spy(),
send: sinon.spy(),
status: function(responseStatus) {
return this;
}
};
req = {
params: {
id: 1
}
};
UserStub.save = function(callback) {
callback(null, req.body);
};
});
describe('add a poll', function() {
it('should add the poll', function() {
req.body = {
name: 'Poll1',
options: [{
text: 'option1',
votes: 0
}, {
text: 'option2',
votes: 1
}]
};
var user = {};
user.polls = [];
UserStub.findById = function(query, callback) {
callback(null, user);
};
sinon.stub(require('../controllers/users'), 'saveOptions').returns([OptionStub, OptionStub]);
sinon.stub(require('../controllers/users'), 'savePoll').returns({});
users.addPoll(req, res);
expect(user.polls).to.equal('[{}]');
});
});
users.js
addPoll: function(req, res) {
var self = this;
User.findById(req.params.user_id, function(err, user) {
if (err) return res.status(400).json(err);
self.saveOptions(req.body.options)
.then(function(optionsArr) {
var pollName = req.body.name;
self.savePoll(pollName, optionsArr)
.then(function(poll) {
user.polls.push(poll);
user.save(function(err, user) {
if (err) return res.status(500).json(err);
res.json(poll);
});
}, function(err) {
return res.status(500).json(err);
});
}, function(err) {
return res.status(400).json(err);
});
});
},
savePoll: function(pollName, optionsArr) {
return new Promise(function(resolve, reject) {
var poll = new Poll();
poll.name = pollName;
poll.options = optionsArr.slice();
poll.save(function(err, poll) {
if (err) {
reject(err);
} else {
resolve(poll);
}
});
});
},
saveOptions: function(options) {
var optsArr = [];
var promises = options.map(function(opt) {
return new Promise(function(resolve, reject) {
var option = new Option(opt);
return option.save(function(err, option) {
if (err) {
reject(Error(err));
} else {
resolve(optsArr.push(option));
}
});
});
});
return Promise.all(promises).then(function() {
return optsArr;
}).catch(function(err) {
console.log(err);
});
}

Categories

Resources