Calling a function return Promise - node.js

I was trying to implement Promises when I get error saying
TypeError: productService.getSwapItems is not a function
I have created a DB class as
const mysql = require('mysql')
class Database {
constructor( config ) {
this.connection = mysql.createConnection( config );
}
query(sql, args) {
return new Promise( (resolve, reject) => {
this.connection.query(sql, args, (error, result) => {
if(error) {
return reject(error);
}
resolve(result);
});
});
}
close() {
return new Promise( (resolve, reject) => {
this.connection.end( err => {
if(err) {
return reject(err);
}
resolve();
})
});
}
}
module.exports = Database;
And then a service class
let Database = require('./mysql.db');
const database = new Database({
host: "db-host",
user: "username",
password: "password",
database: "databasename"
});
class ProductService {
getSwapItems(item_ids_array) {
this.queryStr = "SELECT * FROM Item WHERE from_item_id IN (" + item_ids_array + ")";
return database.query(this.queryStr, null); // database.query returns a promise, so getSwapItems() will also return promise??
}
}
module.exports = ProductService;
When productService using code below, I get error.
var item_ids_array = <Array Values>;
productService.getSwapItems(item_ids_array)
.then( rows => {
console.log(rows);
}).catch( err => {
console.log(err);
});
Error is
productService.getSwapItems(item_ids_array)
TypeError: productService.getSwapItems is not a function

Your module exports a class ProductService with regular method getSwapItems. Classes are first-class citizens in JS, so you can assign them (as value) to any variable any way you like - including require-ing a module.
const ProductService = require('./db/product.service');
But the class is like a recipe for creating an instance of the class you can use, not an instance of the class itself. To actually create an instance of the class, use the new keyword:
const productService = new ProductService();
Only after that you'll be able to use methods of this class the usual way.

Related

Node.js: Custom MySQL connector - Cannot read properties of undefined (reading 'query')

I have created a custom connection module to connect to my MySQL DB. This module will be used by multiple lambda functions. But I am getting the following error:
'Cannot read properties of undefined (reading 'query')'
Here is the module:
const MyApplicationNameSecrets = require('../secrets/MyApplicationNameSecrets');
const mysql = require("mysql");
module.exports = class MySqlConnect {
databaseCredObject;
pool;
constructor() {
this.initPool();
}
async initPool() {
if (!this.databaseCredObject) {
await this.fetchSecret();
}
const connection_settings = {
host: this.databaseCredObject.host,
user: this.databaseCredObject.username,
password: this.databaseCredObject.password,
database: 'mydbname',
connectionLimit: 10 // specify the maximum number of connections in the pool
};
this.pool = mysql.createPool(connection_settings);
}
async queryDb(sql, args) {
return new Promise((resolve, reject) => {
this.pool.query(sql, args, function (err, result) {
console.log('Connected');
if (err) {
console.log('error when connecting to db:', err);
return reject(err);
}
return resolve(result);
});
});
}
async fetchSecret() {
const databaseCredString = await MyApplicationNameSecrets.getSecret('MyApplicationName', 'eu-west-2');
this.databaseCredObject = JSON.parse(databaseCredString);
}
}
Wondering if anyone can help please:
thanks

Mocha's test code using ts-mockito does not work

mocha + chai + ts-mockito is executing unit-test of the node.js application.
In order to test the Service class, we mock the method of the Repository class which is called in the Service class.
Running will not work as expected.
I expect ErrorDto, but the string "TypeError: Can not read property 'then' of null" will be returned.
Please tell me something is wrong.
Test code.
let mockedRepository: MyRepository = tsmockito.mock(MyRepository);
describe("MyService Test", () => {
it("getAll() test", async () => {
const errorDto = new ErrorDto();
errorDto.addMessage("test message");
tsmockito.when(mockedRepository.getAll()).thenCall(() => {
return new Promise((resolve, reject) => {
reject(errorDto);
});
});
let mockedRepositoryInstance: MyRepository = tsmockito.instance(mockedRepository);
Container.set(MyRepository, mockedRepositoryInstance);
let service = new MyService();
let res = await service.getAll();
// I expect ErrorDto for res,
// but "TypeError: Can not read property 'then' of null" is returned.
if (res instanceof ErrorDto) {
// Do not reach below
let msg = res.getMessages();
expect(msg[0]).to.eql("test message");
} else {
// Test failure
expect(true).to.false;
}
});
});
Service class to be tested
#Service()
export default class MyService {
#Log()
public getAll() {
const repository = Container.get(MyRepository);
return new Promise<MyServiceTreeDto[]>((resolve, reject) => {
repository.getAll()
.then((res: MyTreeDomain[]) => {
const resDto = new Array<MyServiceTreeDto>();
//do something...
resolve(resDto);
}).catch((err) => {
reject(err);
});
});
}
}
Repository class to be mocked
#Service()
export default class MyRepository {
public async getAll(domain: MyDomain): Promise<MyTreeDomain[]> {
try {
let res: MyTreeDomain[] = new Array<MyTreeDomain>();
//do async process
return res;
} catch (err) {
if (err instanceof ErrorDto) {
throw err;
} else {
const errorDto = new ErrorDto();
errorDto.addMessage("error!");
throw errorDto;
}
}
}
}

MongoDB in NodeJS Class Structure

Is there a way to use MongoDB in a class structure in NodeJS?
I understand you can do CRUD operations on the DB within the connection method like
mongo.connect(url, function(err, client){//do some CRUD operation});
but I was wondering if there was a way to open the connection to the DB, have access to it across the class, then close it when you are done working with the class.
For example:
class MyClass {
constructor(databaseURL) {
this.url = databaseURL;
}
async init() {
//make connection to database
}
async complete_TaskA_onDB() {
//...
}
async complete_TaskB_onDB() {
//...
}
async close_connection() {
//close connection to database
}
}
Edit:
I just came across more information in the Node.JS Mongo docs. Maybe something along the lines of this would work?
//constructor()
this.db = new MongoClient(new Server(dbHost, dbPort));
//init()
this.db.open();
//taskA()
this.db.collection(...).update(...);
//close_connection()
this.db.close();
You can create a class of which will act as a wrapper on any core lib, doing so will give you below advantages:
Wrapping any core module with your own service will allow you to:
Create a reusable service that you could use on multiple components in your app.
Normalize the module's API, and add more methods your app needs and the module doesn’t provide.
Easily replace the DB module you chose with another one (if needed).
I have created that service which I use it in my projects for MongoDB:
var mongoClient = require("mongodb").MongoClient,
db;
function isObject(obj) {
return Object.keys(obj).length > 0 && obj.constructor === Object;
}
class mongoDbClient {
async connect(conn, onSuccess, onFailure){
try {
var connection = await mongoClient.connect(conn.url, { useNewUrlParser: true });
this.db = connection.db(conn.dbName);
logger.info("MongoClient Connection successfull.");
onSuccess();
}
catch(ex) {
logger.error("Error caught,", ex);
onFailure(ex);
}
}
async getNextSequence(coll) {
return await this.db.collection("counters").findOneAndUpdate({
_id: coll
},
{$inc: {seq: 1}},
{projections: {seq: 1},
upsert: true,
returnOriginal: false
}
);
}
async insertDocumentWithIndex(coll, doc) {
try {
if(!isObject(doc)){
throw Error("mongoClient.insertDocumentWithIndex: document is not an object");
return;
}
var index = await this.getNextSequence(coll);
doc.idx = index.value.seq;
return await this.db.collection(coll).insertOne(doc);
}
catch(e) {
logger.error("mongoClient.insertDocumentWithIndex: Error caught,", e);
return Promise.reject(e);
}
}
async findDocFieldsByFilter(coll, query, projection, lmt) {
if(!query){
throw Error("mongoClient.findDocFieldsByFilter: query is not an object");
}
return await this.db.collection(coll).find(query, {
projection: projection || {},
limit: lmt || 0
}).toArray();
}
async findDocByAggregation(coll, query) {
if(!query.length){
throw Error("mongoClient.findDocByAggregation: query is not an object");
}
return this.db.collection(coll).aggregate(query).toArray();
}
async getDocumentCountByQuery(coll, query) {
return this.db.collection(coll).estimatedDocumentCount(query || {})
}
async findOneAndUpdate(coll, query, values, option) {
if(!(isObject(values) && isObject(query))){
throw Error("mongoClient.UpdateDocument: values and query should be an object");
}
return this.db.collection(coll).findOneAndUpdate(query, {$set : values}, option || {})
}
async modifyOneDocument(coll, query, values, option) {
if(!(isObject(values) && isObject(query))){
throw Error("mongoClient.ModifyOneDocument: values, query and option should be an object");
}
return await this.db.collection(coll).updateOne(query, values, option || {})
}
async close() {
return await this.db.close();
}
}
module.exports = {
mongoDbClient: mongoDbClient
}
For my complete lib access you can refer here
Yes, you can do all of this inside a class, but you cannot set a member variable like db after the constructor has been set. You can make it a global variable, but you cannot set the variable.
const MongoClient = require('mongodb').MongoClient;
var database; //global
class DB {
constructor(url, dbName) {
this.url = url;
this.dbName = dbName;
}
connect() {
console.log('connecting to database ' + this.dbName + ' with URL ' + this.url);
return new Promise((resolve, reject) => {
MongoClient.connect(this.url, (err, client) => {
if (err) {
reject(err);
} else {
database = client.db(this.dbName);
resolve(client.db(this.dbName));
}
});
})
}
}

how can access property of class in promise?

I have a class that have one method .I want change return type of my method to promise . but in promise can not access to properties of class .how can resolve this .But get this exception
Reason: TypeError: Cannot read property 'bot' of undefined
const SDK = require('balebot');
const Promise = require('bluebird');
import incoming from './incoming';
const _ = require('lodash');
class Bale {
constructor(bp, config) {
if (!bp || !config) {
throw new Error('You need to specify botpress and config');
}
this.bot = null;
this.connected = false;
this.bot = new SDK.BaleBot(config.botToken);
bp.logger.info('bale bot created');
}
setConfig(config) {
this.config = Object.assign({}, this.config, config);
}
sendText(chat, text, options) {
let msg = new SDK.TextMessage(text);
return new Promise(function (resolve, reject) {
var response = this.bot.send(msg, receiver);
if (response) {
reject(err);
} else {
resolve(response);
}
});
}
}
module.exports = Bale;
You need to bind this or use Arrow functions to preserve this context:
const SDK = require('balebot');
const Promise = require('bluebird');
import incoming from './incoming';
const _ = require('lodash');
class Bale {
constructor(bp, config) {
if (!bp || !config) {
throw new Error('You need to specify botpress and config');
}
this.bot = null;
this.connected = false;
this.bot = new SDK.BaleBot(config.botToken);
bp.logger.info('bale bot created');
}
setConfig(config) {
this.config = Object.assign({}, this.config, config);
}
sendText(chat, text, options) {
let msg = new SDK.TextMessage(text);
// Use an arrow function instead of function
return new Promise((resolve, reject) => {
var response = this.bot.send(msg, receiver);
if (response) {
reject(err);
} else {
resolve(response);
}
});
}
}
module.exports = Bale;
This would work
sendText() {
return new Promise((resolve, reject) => {
console.log(this.bot); // it will not be undefined
});
}
The reason this works is because arrow functions lexically bind their context so this actually refers to the originating context.

Module.export a 'default' function

I want to write a module that exports a default function and a list of properties (they are functions too).
Basically this module will allows this usage consumer-side:
let db_connection = fake_database_connection_instance()
let db = require('./db.js')(db_connection)
db.find({id:1})
So the default function should only pass the database connection instance to the module.
This is the not working code
module.exports = {
//init should go away and replaced by a 'default' function when this module is called
init: function (connection) {
this.connection= connection;
return this;
},
find: function (query) {
return new Promise(function (resolve, reject) {
this.connection.find(query, function (err, docs) {
if (err) {
return reject(err);
}
return resolve(docs);
});
});
}
}
I want to avoid the new keyword (consumer-side), so I have to remove those this, I know. The problem here are 2:
how to export a default function called on require('./db.js')() and other functions e.g. require('./db.js').find()?
how pass the connection instance from the default function to find()?
EDIT following the #Igor Raush ES6 Class solution I wrote this, but still db is not defined
class DB {
constructor(db) {
this.db = db;
}
find(query) {
return new Promise( (resolve, reject) => {
this.db.find(query, function (err, docs) {
if (err) {
return reject(err);
}
return resolve(docs);
});
});
}
}
You can add a default function to module.exports and use an external variable _conn to store the connection:
let _conn;
module.exports = (connection) => {
_conn = connection;
};
After that you could add find function to module.exports object:
module.exports.find = (query) => {
return new Promise(function(resolve, reject) {
_conn.find(query, function(err, docs) {
if (err) {
return reject(err);
}
resolve(docs);
});
});
}
One option is to create a class and export an instance factory function.
// db.js
function DB(connection) {
this.connection = connection;
}
DB.prototype.find = function find(query) {
let connection = this.connection;
// ...
}
// export instance factory function
module.exports = function (connection) { return new DB(connection); };
or, if you're in an ES6 environment,
// db.js
class DB {
constructor(connection) {
this.connection = connection;
}
find(query) {
let connection = this.connection;
// ...
}
}
// export instance factory function
module.exports = connection => new DB(connection);
Then you can do
let db = require('./db')(db_connection);
db.find({ id: 1 });
If you'd like to enforce the singleton pattern (only one DB instance can exist), lazily create a module-local instance and return the reference on subsequent calls:
let _instance = null;
module.exports = connection => {
// return existing instance, or create a new one
return _instance || (_instance = new DB(connection));
}

Resources