Return async function to express router - node.js

This may sound dum. But what I am stuck here with is
1: returning the converted JSON file
2: getting the returned object into the route
routes.js
const express = require('express');
const router = express.Router();
const routings = require('./src/services/routings');
router.get('/routings', async(req, res) => {
const routesRes = await routings.getRoutings();
res.end(JSON.stringify(routesRes, null, " ")).catch(function (err) {
console.log(err);
});
});
module.exports = router;
routings.js
const parseXml = require('xml2js')
let data = `<?xml version="1.0" encoding="UTF-8"?>...'
getRoutings = async() => {
await parseXml.parseStringPromise(data).then(function (result) {
console.log('Done');
return result;
})
.catch(function (err) {
console.log(err);
});
}
module.exports = {getRoutings}

Your getRoutings() function does not have a return value. Therefore when you do this:
const routesRes = await routings.getRoutings();
routesRes will always be undefined.
I would suggest this:
getRoutings = () => {
return parseXml.parseStringPromise(data).then(function (result) {
console.log('Done');
return result;
}).catch(function (err) {
console.log(err);
throw err; // make sure error is propagated
});
}

Related

mongodb method without function not working in nodejs

I don't know much Javascript and was making a nodejs app. My mongodb query in nodejs is working only when the query has a function method like .toArray
Here's the database.js file
const {MongoClient} = require('mongodb');
const uri = "mongodb+srv://name:pass#clusterurl/metro4?retryWrites=true&w=majority";
// all fields are correctly filled
const client = new MongoClient(uri);
try {
// Connect to the MongoDB cluster
client.connect(err =>{
if(err) throw err;
let db = client.db('metro4');
db.collection('Station').find().toArray(function(err, result){
if(err) throw err;
console.log(result);
});
let a = db.collection('Station').findOne({'_id':4});
if(a) {
console.log(a);
}
else{
console.log("No a\n");
}
module.exports = db;
});
} catch (e) {
console.error(e);
} finally {
client.close();
}
when I run the app, the db.collection('Station').find().toArray runs fine and output the result but the second query of findOne doesn't work.
Any help is appreciated.
The findOne method returns a Promise. You should handle its result in a callback function:
db.collection('Station').findOne({ _id: 4 }, function (err, a) {
if (err) {
console.log(err);
} else if (a) {
console.log(a);
} else {
console.log('No a\n');
}
});
Or using async - await:
client.connect(async (err) => {
...
let a = await db.collection('Station').findOne({ _id: 4 })
...
});
EDIT
To handle the import - export problem you should handle the datase connection operations separate async functions.
You may use the connection function to return the database instance:
const {MongoClient} = require('mongodb');
const uri = "mongodb+srv://name:pass#clusterurl/metro4?retryWrites=true&w=majority";
// all fields are correctly filled
const client = new MongoClient(uri);
const connectDB = async () => {
try {
// Connect to the MongoDB cluster
await client.connect();
return client.db('metro4');
} catch (e) {
throw e;
}
}
const disconnectDB = () => {
client.close();
}
module.exports = { connectDB, disconnectDB };
Then use these functions to handle your database related operations:
const { connectDB, disconnectDB } = require('../database');
const getStations = async () => {
const db = connectDB();
if (!db) return;
try {
const data = await db.collection('Station').find().toArray();
return data;
} catch (err) {
throw err;
} finally {
disconnectDB();
}
}
const getStation = async (id) => {
const db = connectDB();
if (!db) return;
try {
const data = await db.collection('Station').findOne({ _id: id});
return data;
} catch (err) {
throw err;
} finally {
disconnectDB();
}
}

In Node js, why is my controller.js not showing the data after invoking a model.js function

I'm new to Nodejs and i've fetched a json file using require and fs, and using MVC , i have a model.js that has the function that reads the json file, but when the controller invokes the model function, the data is not shown (console.log(data) in the controller, but console.logged in the model.js. Here is my code:
controller.js
exports.renderHomePage = (req, res) => {
apiServerModel.loadTeams()
.then(function (data) {
console.log(data);
console.log("This is inside controller")
res.render("index", { // output as string
teamsList: `${JSON.stringify(data, null, 2)}` // A property called teamList to be displayed on the browser
//teamsList: teamView.TeamsView(`${JSON.stringify(data, null, 2)}`)
})
})
.catch(error => console.log(error))
}
model.js
'use strict';
const fs = require('fs');
class TeamsModel {
static async loadTeams() {
try {
await fs.readFile('./json/prov-nodes.json', (err, rawData) => {
if (err) throw err;
let teams = JSON.parse(rawData);
return teams;
//console.log(teams);
});
} catch (error) {
console.log(error)
}
console.log('This is after the read call');
}
}
exports.TeamsModel = TeamsModel;
first of all you should read about Callbacks VS Promises VS Async/Await in node.js.
when you use async/await, don't have a callback so:
use fs/promises instead of fs, because you used await for fs in your loadTeams function.
'use strict';
const fs = require('fs/promises');
class TeamsModel {
static async loadTeams() {
console.log("hi")
try {
let rawData = await fs.readFile('./json/prov-nodes.json')
let teams = JSON.parse(rawData);
return teams;
} catch (error) {
console.log(error)
}
console.log('This is after the read call');
}
}
module.exports = TeamsModel;

Nodejs formidable async mongoose await issue

I'm trying to make a OCR parse of an image.
All the things works well but I have a problem this mongoose and syncronysm.
But cannot use "await" on the mongoose find call as the function is not async. How do I solve that.
Here is my code:
// post processImage
router.post('/', async (req, res) => {
try {
var baseUrl;
const form = formidable({ multiples: true });
form.parse(req, function (error, fields, files) {
var imatgeAProcessar = files.image.path;
var extname = path.extname(files.image.name);
getTextFromImage(imatgeAProcessar) // OCR process of the image
.then(res => {
const boss_name_req = res.boss_name;
const boss = Boses.findOne({"name" : boss_name_req}).exec();
// ERROR HERE // return nothing althought it exist on database (no await?)
console.log(JSON.stringify(boss)); // writes "{}"
const processedImage = {
"success": true,
"boss_name": boss.name,
"boss_image": baseUrl + 'images/' + boss.num + ".png"
}
res.json(processedImage);
})
});
} catch (err) {
res.json({message: err});
}
});
*edited
// post processImage
router.post('/', async(req, res) => {
try {
var baseUrl;
const form = formidable({ multiples: true });
var formfields = await new Promise(function(resolve, reject) {
form.parse(req, function(err, fields, files) {
if (err) {
reject(err);
return;
}
resolve(files);
}); // form.parse
});
var imatgeAProcessar = formfields.image.path;
var extname = path.extname(formfields.image.name);
const res = await getTextFromImage(imatgeAProcessar)
const boss_name_req = res.boss_name;
const boss = await Boses.findOne({ "name": boss_name_req }).limit(4).skip(0).exec();
const processedImage = {
"success": true,
"boss_name": boss.name,
"boss_image": baseUrl + 'images/' + boss.num + ".png"
}
res.json(processedImage)
} catch (err) {
res.json({ message: err });
}
});
Finally I found the way... I wrote a callback on the findOne call as :
const boss = Boss.findOne({"name" : boss_name_req})
.then( resMongoose => {
try {
const processedImage = {
"success": true,
"gym": resOCR.gym,
"boss_name": resMongoose.name,
}
res.json(processedImage);
} catch (err) {
res.json({message: err});
}
});

How do I access the data array in my axios function

I'm trying to access the data array that has the information from the request, but I cannot seem to figure it out.
When I try to display it in my view, I get an undefined cannot read property of length;
const getData = () => {
axios.get("https://www.worldometers.info/coronavirus/")
.then(res => {
const data = [];
const $ = cheerio.load(res.data);
$('.maincounter-number').each((index, element) => {
const numberData = $(element).text();
data[0] = {numberData: numberData};
});
}).catch(err => {
console.log("Error fetching and parsing data: ", err);
});
}
app.get("/", (req, res) => {
const data = getData();;
res.render('index', {title: 'Home', data: data});
});
Pug View
p #{data.dataNumbers}
I've also tried this function as well but I get the same issue
async function scrapeWorldOMeter(){
try{
const worldOMeterResponse = await axios.get("https://www.worldometers.info/coronavirus/");
const data = [];
const $ = cheerio.load(worldOMeterResponse.data);
$('.maincounter-number').each((index, element) => {
const numberData = $(element).text();
data[0] = {numberData: numberData};
return data[0];
});
}
catch(err){
throw new Error(`Can't scrape WorldOMeter ${err}`)
}
}
app.get("/", async(req, res) => {
const data = await scrapeWorldOMeter()
res.render('index', {title: 'Home', data});
});
You can fix your first code snippet by using promises properly,
const getData = () => {
return axios
.get('https://www.worldometers.info/coronavirus/')
.then((res) => {
const data = [];
const $ = cheerio.load(res.data);
$('.maincounter-number').each((index, element) => {
const numberData = $(element).text();
data[0] = { numberData: numberData };
});
return data[0];
})
.catch((err) => {
console.log('Error fetching and parsing data: ', err);
});
};
app.get('/', (req, res) => {
const data = getData().then((res) => {
res.render('index', { title: 'Home', data: data });
});
});
The problem here was that you were not returning the axios promise in getData() and also you were not chaining the response with then in the get route.
Your second code snippet can be fixed by using async/await properly,
async function scrapeWorldOMeter() {
try {
const worldOMeterResponse = await axios.get(
'https://www.worldometers.info/coronavirus/'
);
const data = [];
const $ = cheerio.load(worldOMeterResponse.data);
$('.maincounter-number').each((index, element) => {
const numberData = $(element).text();
data[0] = { numberData: numberData };
});
return data[0];
} catch (err) {
throw new Error(`Can't scrape WorldOMeter ${err}`);
}
}
app.get("/", async(req, res) => {
const data = await scrapeWorldOMeter();
res.render('index', {title: 'Home', data});
});
The problem here was that you were returning data[0] inside the jquery each loop. You simply have to move it to the next line outside the loop

Avoiding callback hell

I have this code that serves every markdown file in the './markdown' folder. At '/api/markdown/filename'.
var apiRouter = express.Router();
markdownFolder = './markdown/';
apiRouter.get('/:markdown_file_noext', function(req, res) {
fs.readdir(markdownFolder, function(err, markdown) {
if (err) throw err;
markdown.forEach(function(file) {
fs.readFile(markdownFolder + file, 'utf8', function(err, file_content) {
if (err) throw err;
fileNoExtension = file.slice(0, file.indexOf('.'));
if (req.params.markdown_file_noext == fileNoExtension) {
res.json({
'title': fileNoExtension,
'markdown': marked(file_content)
});
};
});
});
});
});
But i end having a ton of callbacks do the the nature of the 'fs' methods. How do i avoid this?
Using Q as promise library:
const Q = require('q');
const fs = require('fs');
const markdownFolder = './markdown/';
const readdir = Q.nfbind(fs.readdir);
const readFile = Q.nfbind(fs.readFile);
readdir(markdownFolder).then(markdown => {
const promises = [];
markdown.forEach(file => promises.push(readFile(markdownFolder + file, 'utf8')));
return Q.all(promises);
}).then(files => {
// Do your magic.
}).catch(error => {
// Do something with error.
});
You have different option.
Use named Function instead of anonymus functinos. It would make it a little bit more readable but you will still be using callbacks.
Use Promises, but you will need to use bluebird to wrap the fs module.
For a more advance option, you can use generators and Promises to make your code look more like a sync way. Take a look at co or bluebird.coroutine.
With Promises you could do like this:
const path = require('path');
var apiRouter = express.Router();
markdownFolder = './markdown/';
apiRouter.get('/:markdown_file_noext', function(req, res) {
readdir(markdownFolder)
.then((files) => {
const tasks = files.map((file) => {
const filePath = path.resolve(markdownFolder, file);
return readFile(filePath);
});
return Promise.all(tasks); // Read all files
})
.then((fileContents) => {
return fileContents.map((content) => {
fileNoExtension = file.slice(0, file.indexOf('.'));
if (req.params.markdown_file_noext == fileNoExtension) {
return {
'title': fileNoExtension,
'markdown': marked(content)
};
};
})
})
.then((results) => {
// It's better if you aggregate all results in one array and return it,
// instead of calling res.json for each result
res.json(results);
})
.catch((err) => {
// All errors are catched here
console.log(err);
})
});
function readdir(folderPath) {
return new Promise((resolve, reject) => {
fs.readdir(folderPath, (err, files) {
if (err) {
return reject(err);
}
resolve(files);
});
});
}
function readFile(filePath) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (err, file_content) => {
if (err) {
return reject(err);
}
resolve(file_content);
});
});
}

Resources