Exporting a function declared inside asynchronous function - node.js

I have a file in which mongoose is setup
const keys = require('../chat/config/keys');
const mongoose = require('mongoose');
const dbURI = keys.mongoURI;
mongoose.connect(dbURI, { useNewUrlParser: true });
mongoose.set('debug', true);
let fetchVideo;
mongoose.connection.once('open', function () {
console.log('Mongoose default connection open to ' + dbURI);
let connectToDB = mongoose.connection.db;
let videoChatDB = connectToDB.collection('videochat');
fetchVideo = ({ id }) => {
if (id !== '100') {
videoChatDB.findOne({'studentID': parseInt(id)}).then((user) => {
if (user) {
console.log(user);
return true;
} else {
console.log(user);
return false;
}
});
}
}
});
module.exports = { fetchVideo };
And I am requiring that file inside my index.js file like so:
let db = require('./db');
In my index file I have a socket connection and I need to check the database when a new user comes.
socket.on('new-user', async (user) => {
let checkAvail = await db.fetchVideo(user);
});
But I am getting this error:
TypeError: db.fetchVideo is not a function
I am guessing it is undefined since it is declared inside an asynchronous function.
How would I make this to work?

Because the function is created asynchronously, one option is to export a Promise that resolves to fetchVideo function. Because mongoose.connection.once is callback-based, you'll have to transform it into a Promise.
If you want fetchVideo to resolve to something (rather than nothing), you also have to properly chain the findOne call with the promise chain; to fix that, return videoChatDB.findOne....
const fetchVideoProm = new Promise((res, rej) => {
mongoose.connection.once('open', function () {
console.log('Mongoose default connection open to ' + dbURI);
let connectToDB = mongoose.connection.db;
let videoChatDB = connectToDB.collection('videochat');
const fetchVideo = ({ id }) => {
if (id !== '100') {
return videoChatDB.findOne({'studentID': parseInt(id)}).then((user) => {
if (user) {
console.log(user);
return true;
} else {
console.log(user);
return false;
}
});
}
}
res(fetchVideo);
});
});
module.exports = { fetchVideoProm };
Consume it by awaiting the creation of the fetchVideo function, and then calling it:
socket.on('new-user', async (user) => {
const fetchVideo = await db.fetchVideoProm;
const checkAvail = await fetchVideo(user);
});

Related

Database returned value is undefined in Node JS

I have a encountered a problem while learning NodeJS. I am new to javascript so can anybody explain this to me.
I am calling a function from index.js which will returns values fetched from database. But I am getting undefined object on return.
index.js
const cron = require('node-cron');
const app = require('./app');
const port = process.env.PORT || 3000;
require('dotenv').config();
const { getRecentData } = require('./service/getRecentData')
var prev_id = 0;
cron.schedule('*/10 * * * * *', async() => {
var row = await getRecentData();
console.log(row);
var new_id = row["id"];
if (new_id == prev_id){
new_id = prev_id;
}
else{
console.log(row["date_time"]);
prev_id = new_id;
}
})
app.listen(port, () => {
console.log(`Listening: http://localhost:${port}`);
});
getRecentData.js
const e = require("express")
const { Client } = require('pg');
const client = new Client({
user: 'postgres',
host: 'localhost',
database: 'database',
password: 'postgres',
port: 5432,
});
client.connect();
const getRecentData = async () => {
const query = `
SELECT *
FROM test_table order by id desc limit 1
`;
const recentData = await client.query(query, (err, res) => {
if (err) {
console.error(err);
return;
}
return res.rows;
});
return recentData;
}
module.exports = {
getRecentData
}
But I am getting undefined object in index.js when calling getRecentData() function.
You need to understand that await works only with Promises. If the function returns a Promise, you can use await. All async functions return a Promise. In your case, client.query is not returning a promise, it has a callback which has it's own execution context, hence return recentData statement is executing before the completion of the client.query method. You can modify your getRecentData method like this
const getRecentData = () => {
return new Promise((resolve, reject) => {
const query = `SELECT * FROM test_table order by id desc limit 1`;
client.query(query, (err, res) => {
if (err) {
console.error(err);
reject(err);
return;
}
resolve(res);
});
});
};
don't use recentData variable
await client.query(query, (err, res) => {
if (err) {
console.error(err);
return;
}
if(res){
return res.rows;
}
});

Can't return data from callback function at NodeJS

When i create new variable and assign callback function, But data cannot return from callback function. Undefined is occurring at new variable.
const nedb = require('nedb');
const user = new nedb({ filename: './builds/database/users.db', autoload: true });
var s = user.find({}, function (err,docs) {
if(docs.length == 0) {
var data = false;
} else {
var data = docs;
}
return data;
});
console.log(s);
var s is undefined! ....
You are mixing up callback and Promise which are two different way to handle asynchronous calls.
I recommend you to use of Promises because they are simpler and the present and future of javascript.
Using async/await which is the next step after Promises
const user = {
find: () => ['jon', 'kalessi', 'jorah'],
};
async function getUsers() {
return (await user.find({})) || [];
}
async function myJob() {
const users = await getUsers();
console.log(users);
// Put your next code here
}
myJob();
Full promise :
const user = {
find: () => new Promise((resolve) => resolve(['jon', 'kalessi', 'jorah'])),
};
user.find({})
.then((docs = []) => {
console.log(docs);
// Put you next code here, you can use of the variable docs
})
.catch((err) => {
console.log(err);
});
Full callback :
const user = {
find: (_, callback) => callback(false, ['jon', 'kalessi', 'jorah']),
};
user.find({}, (err, docs = []) => {
if (err) {
console.log(err);
} else {
console.log(docs);
// Put you next code here, you can use of the variable docs
}
});
I think user.find returning the promise. you can do like this.
const nedb = require('nedb');
const user = new nedb({ filename: './builds/database/users.db', autoload: true });
var s = user.find({}, function (err,docs) {
if(docs.length == 0) {
var data = false;
} else {
var data = docs;
}
return data;
});
Promise.all(s)
.then(result => {
console.log(result);
});
Otherwise you can also use await Like this:
async function abc(){
const nedb = require('nedb');
const user = new nedb({ filename: './builds/database/users.db', autoload: true });
var s = await user.find({}, function (err,docs) {
if(docs.length == 0) {
var data = false;
} else {
var data = docs;
}
return data;
});
}
because await worked with async thats why i put it into async function.

how to stop process nodejs within promises

I've created nodejs to trigger(with cronjobs) firebase realtime database as follow:
var db = admin.database();
var ref = db.ref('myusers');
var promises = [];
function updateUnlocked(isLocked, locked, msisdn) {
return new Promise(function (resolve, reject) {
if (isLocked === 1) {
var startDate = moment(locked);
var endDate = moment();
var result = endDate.diff(startDate, 'minutes');
if (result > 5) {
var ref = db.ref('myusers/' + msisdn);
ref.update({isLocked: 2});
}
}
resolve('done');
});
}
ref.once('value', function(snapshot) {
snapshot.forEach(childSnapshot => {
promises.push(updateUnlocked(childSnapshot.val().isLocked, childSnapshot.val().locked, childSnapshot.key));
});
});
Promise.all(promises).then(function(data) {
console.log(data);
}).catch(function(err) {
console.log('error');
});
Please let me know where can I add process.exit(). Thanks.
You must wait for the "once" callback to get executed. Else the promise array is empty and the process could exit immediately.
var db = admin.database();
var ref = db.ref('myusers');
function updateUnlocked(isLocked, locked, msisdn) {
...
}
ref.once('value', function(snapshot) {
const promises = snapshot.map(childSnapshot => {
return updateUnlocked(childSnapshot.val().isLocked, childSnapshot.val().locked, childSnapshot.key);
})
Promise.all(promises).then(() => {
console.log('done')
process.exit(0)
}).catch(err => {
console.log('error', err)
process.exit(1)
})
});
Demonstrating the control flow.
setTimeout(() => {
const x = [1, 2, 3]
const promises = x.map(i => {
return new Promise(resolve => resolve(i))
})
Promise.all(promises).then(() => {
console.log('done. process.exit(0) here')
})
}, 200)
If you want to exit on successful completion then refer below code:
Promise.all(promises).then(function(data) {
console.log(data);
process.exit(0);
}).catch(function(err) {
console.log('error');
});
If you want to exit on error as well then add process.exit(1) in catch block.

koa2+koa-router+mysql keep returning 'Not Found'

Background
I am using koa2 with some middlewares to build a basic api framework. But when I use "ctx.body" to send response in my router, the client side always receive "Not Found"
My code
./app.js
const Koa = require('koa');
const app = new Koa();
const config = require('./config');
//Middlewares
const loggerAsync = require('./middleware/logger-async')
const bodyParser = require('koa-bodyparser')
const jsonp = require('koa-jsonp')
app.use(loggerAsync())
app.use(bodyParser())
app.use(jsonp());
//Router
const gateway = require('./router/gateway')
app.use(gateway.routes(), gateway.allowedMethods());
app.use(async(ctx, next) => {
await next();
ctx.response.body = {
success: false,
code: config.code_system,
message: 'wrong path'
}
});
app.listen(3000);
./router/gateway.js
/**
* Created by Administrator on 2017/4/11.
*/
const Router = require('koa-router');
const gateway = new Router();
const df = require('../db/data-fetcher');
const config = require('../config');
const moment = require('moment');
const log4js = require('log4js');
// log4js.configure({
// appenders: { cheese: { type: 'file', filename: 'cheese.log' } },
// categories: { default: { appenders: ['cheese'], level: 'error' } }
// });
const logger = log4js.getLogger('cheese');
logger.setLevel('ERROR');
gateway.get('/gateway', async(ctx, next) => {
let time = ctx.query.time;
if (!time) {
ctx.body = {
success: false,
code: config.code_system,
message: 'Please input running times'
}
} else {
try {
let r = await df(`insert into gateway (g_time, g_result, g_date) values (${time}, '',now())`);
return ctx.body = {
success: true,
code: config.code_success
}
} catch (error) {
logger.error(error.message);
}
}
});
module.exports = gateway;
Then a db wrapper(mysql)
./db/async-db.js
const mysql = require('mysql');
const config = require('../config');
const pool = mysql.createPool({
host: config.database.HOST,
user: config.database.USERNAME,
password: config.database.PASSWORD,
database: config.database.DATABASE
})
let query = (sql, values) => {
return new Promise((resolve, reject) => {
pool.getConnection(function (err, connection) {
if (err) {
reject(err)
} else {
connection.query(sql, values, (err, rows) => {
if (err) {
reject(err)
} else {
resolve(rows)
}
connection.release()
})
}
})
})
}
module.exports = query
./db/data-fetcher.js
const query = require('./async-db')
async function performQuery(sql) {
let dataList = await query(sql)
return dataList
}
module.exports = performQuery;
My running result
When I launch server on port 3000 then accesss via http://localhost:3000/gateway?time=5, it always returns "Not found". But as I can see I have already used
return ctx.body = {
success: true,
code: config.code_success
}
to send response. I debugged and found that the database processing was done well, the new data was inserted well.
when I remove that db inserting line, it works well and returns success info.
let r = await df(`insert into gateway (g_time, g_result, g_date) values (${time}, '',now())`);
Is there anything wrong?
Thanks a lot!
Update 2017/04/27
Now I have found the problem. It's due to my custom middleware
const loggerAsync = require('./middleware/logger-async')
Code are like following -
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return function ( ctx, next ) {
return new Promise( ( resolve, reject ) => {
// 执行中间件的操作
log( ctx )
resolve()
return next()
}).catch(( err ) => {
return next()
})
}
}
I changed it to async/await way then everything is working well.
Could anyone please tell me what's wrong with this middleware?
I guess, your problem is the ./db/data-fetcher.js function. When you are calling
let r = await df(`insert ....`)
your df - function should return a promise.
So try to rewrite your ./db/data-fetcher.js like this (not tested):
const query = require('./async-db')
function performQuery(sql) {
return new Promise((resolve, reject) => {
query(sql).then(
result => {
resolve(result)
}
)
}
}
module.exports = performQuery;
Hope that helps.
correct middleware:
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return function ( ctx, next ) {
log( ctx );
return next()
}
}
reason: when resolve involved; promise chain was completed; response has been sent to client. although middleware remained will involved, but response has gone!
try to understand It seems that if you want to use a common function as middleware, you have to return the next function
nodejs(koa):Can't set headers after they are sent

how to connect to mongodb synchronously in nodejs

I want to make use of the promises feature where in I can connect to mongodb synchronously and I can reuse the connection by passing it on to different modules.
Here is something that I came up with
class MongoDB {
constructor(db,collection) {
this.collection = db.collection(collection);
}
find(query, projection) {
if(projection)
return this.collection.find(query, projection);
else
return this.collection.find(query);
}
}
class Crew extends MongoDB {
constructor(db) {
super(db,'crews');
}
validate() {
}
}
I want to setup a connection somewhere in my initial code like the one below and then reuse the connection for different classes, just like how mongoose or monk does but using only the node-mongodb-native package.
MongoClient.connect(url)
.then( (err,dbase) => {
global.DB = dbase;
});
var Crew = new CrewModel(global.DB);
Crew.find({})
.then(function(resp) {
console.log(resp);
});
Right now, the db returns undefined inside the main MongoDB class and am not able to debug this one out through google or the documentation.
Edit: I had assumed that a promise was synchronous but that is not the case.
To reuse the connection I would create a module like this.
module.exports = {
connect: function(dbName, callback ) {
MongoClient.connect(dbName, function(err, db) {
_db = db;
return callback( err );
});
},
getDb: function() {
return _db;
}
};
After that you can connect to the database before starting your application
MongoConnection.connect("mongodb://localhost:27017/myDatabase", function(err){
app.listen(3000, function () {
// you code
});
});
Considering you created the module in a js file you can simply use require to get the databaseConnection
var dbConnection = require("./myMongoConnection.js");
and to get the connection use
var db = MongoConnection.getDb();
Another option using ES6 classes creates a singleton object that you can access repeatedly. It's inspired by #user3134009's answer here.
const EventEmitter = require('events');
const MongoClient = require('mongodb').MongoClient;
const config = require('config');
let _db = null;
class MongoDBConnection extends EventEmitter {
constructor() {
super();
this.emit("dbinit", this);
if (_db == null) {
console.log("Connecting to MongoDB...");
MongoClient.connect(config.dbs.mongo.url, config.dbs.mongo.options,
(err, db) => {
if (err) {
console.error("MongoDB Connection Error", err);
_db = null;
} else {
console.log("Connected to MongoDB", config.dbs.mongo.url);
db.on('close', () => { console.log("MongoDB closed", arguments); _db = null; });
db.on('reconnect', () => { console.log("MongoDB reconnected", arguments); _db = db; });
db.on('timeout', () => { console.log("MongoDB timeout", arguments); });
_db = db;
this.emit('dbconnect', _db);
}
});
}
}
getDB() {
return _db;
}
}
module.exports = new MongoDBConnection();
I have been struggling with this problem for a while, and in particular with setting up and persisting MongoDb connection in AWS lambda functions across invocations.
Thanks to #toszter answer I've finally come up with the following solution:
const mongodb = require('mongodb');
const config = require('./config.json')[env];
const client = mongodb.MongoClient;
const mongodbUri = `mongodb://${config.mongo.user}:${config.mongo.password}#${config.mongo.url}/${config.mongo.database}`;
const options = {
poolSize: 100,
connectTimeoutMS: 120000,
socketTimeoutMS: 1440000
};
// connection object
let _db = null;
class MongoDBConnection {
constructor() {}
// return a promise to the existing connection or the connection function
getDB() {
return (_db ? Promise.resolve(_db) : mConnect());
}
}
module.exports = new MongoDBConnection();
// transforms into a promise Mongo's client.connect
function mConnect() {
return new Promise((resolve, reject) => {
console.log('Connecting to Mongo...');
client.connect(mongodbUri, options, (error, db) => {
if (error) {
_db = null;
return reject(error);
}
else {
console.log('Connected to Mongo...');
_db = db;
resolve(db);
}
});
});
}
To use it in a controller or app.js:
const mongoConfig = require('mongoConfig');
mongoConfig.getDB()
.then(db => db.collection('collection').find({}))
.catch(error => {...});

Resources