Express JS page rendered before async function was executed - node.js

I have a problem with express.js routing. I want to get all restaurants when users open /restaurant/map. Problem is that function gm.geocode is async so page is rendered before that function is executed. How would I get variabile mapsData at the right time, so when page /restaurant/map is loaded? What is the right way to do that?
var express = require('express');
var router = express.Router();
var gm = require('googlemaps');
var fs=require('fs');
var staticDB=require('./staticDB.js');
var mapsData=[];
/* GET home page. */
router.get('/', function (req, res, next) {
//not important
});
router.get('/map/', function (req, res, next) {
var counter=0;
console.log('Request URL:', req.originalUrl);
for (i = 0; i < staticDB.addresses.addresses.length; i++) {
gm.geocode(staticDB.addresses.addresses[i].street + ", " + staticDB.addresses.addresses[i].city, function (err, data){
mapsData.push({
"name": staticDB.restaurants.restaurants[counter].name,
"location": data.results[0].geometry.location
});
counter++;
}, false);
}
res.render('map', {
title: 'title',
restaurants: mapsData
});
});
module.exports = router;

Never use regular for loops to deal with asynchronous flows. There are modules that handle those scenarios for you, like async, or even promises. In your case, your code should look like this, using async.each (I didn't test it):
router.get('/map/', function (req, res, next) {
var counter=0;
console.log('Request URL:', req.originalUrl);
async.each(staticDB.addresses.addresses, function (address, cb) {
gm.geocode(address.street + ", " + address.city, function (err, data){
mapsData.push({
"name": staticDB.restaurants.restaurants[counter].name,
"location": data.results[0].geometry.location
});
counter++;
cb();
}, false);
}, function (err) {
if (err) {
return res.render('error', { error: err });
}
res.render('map', {
title: 'title',
restaurants: mapsData
});
});
});

Related

How to pass model result to view in node js

I m new to node.js, try to making some small app.All is good but i m not able to pass model data outside the function:
Working Code is:
usermodel = require('../models/usermodel.js');
exports.index = function(req, res) {
var stuff_i_want = '';
usermodel.userlist(req, function(result)
{
//stuff_i_want = result;
res.render('index.ejs', {
title: "Welcome to Socka | View Players",
players:result
});
//rest of your code goes in here
});
console.log(stuff_i_want);
};
but i want some thing like:
usermodel = require('../models/usermodel.js');
exports.index = function(req, res) {
var stuff_i_want = '';
usermodel.userlist(req, function(result)
{
stuff_i_want = result;
//rest of your code goes in here
});
console.log(stuff_i_want);
res.render('index.ejs', {
title: "SA",
players:result
});
};
You are calling an asynchronous function userlist that's why you need it inside the callback. You can use Promise libraries like bluebirdjs and refactor your usermodel implementation to standard nodejs function arguments (err, result). In this case you can refactor it like the this below
const usermodel = require('../models/usermodel.js');
exports.index = async function(req, res) {
const players = await new Promise((resolve, reject) => {
usermodel.userlist(req, function(result){
resolve(result)
});
})
res.render('index.ejs', {
title: "SA",
players
});
};

Node.JS downloading hundreds of files simultaneously

I am trying to download more that 100 files at the same time. But when I execute the downloading function my macbook freezes(unable to execute new tasks) in windows also no download(but doesn't freeze) and no download progress in both case(idle network).
Here is my download module:
var express = require('express');
var router = express.Router();
var fs = require('fs');
var youtubedl = require('youtube-dl');
var links = require('../models/Links');
router.get('/', function (req, res, next) {
links.find({dlStatus: false}, function (err, docs) {
if (err) {
console.log(err);
res.end();
} else if (!docs) {
console.log('No incomplete downloads!');
res.end();
} else {
for (var i = 0; i < docs.length; i++) {
//todo scraping
var video = youtubedl(docs[i].url, [], {cwd: __dirname});
// Will be called when the download starts.
video.on('info', function (info) {
console.log('Download started');
console.log(info);
});
video.pipe(fs.createWriteStream('./downloads/' + docs[i].id + '-' + i + '.mp4'));
video.on('complete', function complete(info) {
links.findOneAndUpdate({url: info.webpage_url}, {dlStatus: true}, function (err, doc) {
if (err)console.log(err);
else console.log('Download completed!')
});
});
}
}
});
});
module.exports = router;
Now can anyone please help me here? I am using this module for downloading files.
The solution is using async in this case.
Try it this way....with async.each()
var express = require('express');
var router = express.Router();
var fs = require('fs');
var youtubedl = require('youtube-dl');
var links = require('../models/Links');
var async = require('async')
router.get('/', function (req, res, next) {
links.find({dlStatus: false}, function (err, docs) {
if (err) {
console.log(err);
res.end();
} else if (!docs) {
console.log('No incomplete downloads!');
res.end();
} else {
async.each(docs,function(doc,cb){
var video = youtubedl(doc.url, [], {cwd: __dirname});
// Will be called when the download starts.
video.on('info', function (info) {
console.log('Download started');
console.log(info);
});
video.pipe(fs.createWriteStream('./downloads/' + docs.id + '-' + i + '.mp4'));
video.on('complete', function complete(info) {
links.findOneAndUpdate({url: info.webpage_url}, {dlStatus: true}, function (err, doc) {
if (err){
console.log(err);
cb(err);
}
else {
console.log('Download completed!');
cb()
}
});
});
},function(err){
if(err)
return console.log(err);
console.log("Every thing is done,Here!!");
})
}
});
});
module.exports = router;
And you can process every thing in batch too using async.eachLimits().

NodeJS callback - Access to 'res'

I am using the Express framework and I have the following in one of my route files:
var allUsersFromDynamoDb = function (req, res) {
var dynamodbDoc = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: "users",
ProjectionExpression: "username,loc,age"
};
dynamodbDoc.scan(params, function (err, data) {
if (err) {
console.error("Unable to query. Error:", JSON.stringify(err));
res.statusCode = 500;
res.send("Internal Server Error");
} else {
console.log("DynamoDB Query succeeded.");
res.end(JSON.stringify(data.Items));
}
});
}
I am using the above function in one of my routes:
router.get('/users', allUsersFromDynamoDb);
Now the callback that I am defining while making a call to the "scan" on dynamodbDoc can be pretty useful if defined as a separate function. I can re-use that for some of my other routes as well.
But how can I can still get access to the "res" inside this new function?
I think I should be using "closure" but I can't seem to get it exactly right. I think I would need to maintain the signature of the new callback function to expect 2 params, "err" and "data" as per the following page:
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property
Any ideas on how this can be done?
You can use that function as middleware of every routes you want http://expressjs.com/en/guide/using-middleware.html
The new route with the middleware:
var middlewares = require('./middlewares'),
controllers = require('./controllers');
router.get('/users', middlewares.allUsersFromDynamoDb, controllers.theRouteController);
The middleware (middlewares.js) where you pass your data to req so you can use that data everywhere you have req:
exports.allUsersFromDynamoDb = function (req, res, next) {
var dynamodbDoc = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: "users",
ProjectionExpression: "username,loc,age"
};
dynamodbDoc.scan(params, function (err, data) {
if (err) {
console.error("Unable to query. Error:", JSON.stringify(err));
next("Internal Server Error");
} else {
console.log("DynamoDB Query succeeded.");
req.dataScan = JSON.stringify(data.Items);
next();
}
});
};
And finally the controller (controllers.js):
exports.theRouteController = function (req, res) {
// Here is the dataScan you defined in the middleware
res.jsonp(req.dataScan);
};
Based on Michelem's answer here I tried something which makes things a bit cleaner and code more reusable:
var allUsersFromDynamoDb = function (req, res, next) {
var dynamodbDoc = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: "users",
ProjectionExpression: "username,loc,age"
};
dynamodbDoc.scan(params, function (err, data) {
req.err = err;
req.data = data;
next();
});
}
Now I declare another function:
var processUserResults = function (req, res, next) {
if (req.err) {
console.error("Unable to query. Error:", JSON.stringify(req.err));
res.statusCode = 500;
res.send("Internal Server Error");
} else {
console.log("DynamoDB Query succeeded.");
res.end(JSON.stringify(req.data.Items));
}
};
And finally this:
router.get('/users', [allUsersFromDynamoDb, processUserResults]);
All I need to do in the original "function(err, data)" callback is always set 2 values:
req.err = err
req.data = data
And call next(). And processUserResults can similarly be used for other routes.
Still curious to find out if there are any other efficient solutions.

rendering parties of other site with express

I have a basic Express application with one function that uses nodejs request and takes some divs using selectors. After that, I want to render this with jade.
var express = require('express');
var voc = require('vocabulaire');
var async = require('async');
var router = express.Router();
router.get('/', function (req, res) {
res.render('index', {title: 'Espace de la diffusion'});
});
var result;
router.get('/search/:mot', function (req, res) {
async.series([
function () {
result = main(['conj', req.params.mot]);
console.log('in 1');
},
function () {
res.render('index', {title: 'Espace de la diffusion', data: result});
res.send(html);
console.log('in 2');
},
]);
});
module.exports = router;
var request = require('request')
, cheerio = require('cheerio');
function doit(verbe, result) {
var url = 'http://www.babla.ru/%D1%81%D0%BF%D1%80%D1%8F%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F/%D1%84%D1%80%D0%B0%D0%BD%D1%86%D1%83%D0%B7%D1%81%D0%BA%D0%B8%D0%B9/' + verbe;
request(url, function (err, resp, body) {
$ = cheerio.load(body);
var temps = $('.span4.result-left h5');
if (temps.length == 0) {
console.log('results not found');
}
else {
console.log('result found');
debugger;
return $('.span4.result-left');
}
});
}
function main(arg) {
switch (arg[0]) {
case 'conj':
return doit(arg[1]);
break;
default:
console.log('unknown parameter');
break;
}
}
I used async library for be sure that my result is ready to be rendered but in console I see next:
GET /search/est - - ms - -
in 1
result found
and debugger followed me to nodejs function makeTick()..
I don't know what to do.. help me please.
Your async.series() functions are missing the callback parameter that you need to call in order for the next function to execute. However, you don't really need async to just do a single async task:
main(['conj', req.params.mot], function(err, result) {
res.render('index', {title: 'Espace de la diffusion', err: err, data: result});
});
// ...
function doit(verbe, result, callback) {
var url = 'http://www.babla.ru/%D1%81%D0%BF%D1%80%D1%8F%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F/%D1%84%D1%80%D0%B0%D0%BD%D1%86%D1%83%D0%B7%D1%81%D0%BA%D0%B8%D0%B9/' + verbe;
request(url, function (err, resp, body) {
if (err)
return callback && callback(err);
$ = cheerio.load(body);
var temps = $('.span4.result-left h5');
if (temps.length == 0) {
callback && callback();
}
else {
callback && callback(null, $('.span4.result-left'));
}
});
}
function main(arg, callback) {
switch (arg[0]) {
case 'conj':
doit(arg[1], callback);
break;
default:
callback && callback(new Error('unknown parameter'));
break;
}
}

Closure find node.js, mongodb, express

Please help me. I need variable in my search use post:
app.post('/find', function(req, res) {
var id_school = req.body.std_id;
console.log('show '+ id_sekolah);
db.collection('ak_test_score', function(err, collection) {
collection.find({'std_id':id_school}).toArray(function(err, level) {
var a = level.std_id;
var b = level.school_name;
});
});
res.redirect('/test_score'); // send to my page to get
};
var test = a; // not defined variable a not have
app.get('/test_score', function(req, res) {
var id_school = test;
console.log('show '+ id_sekolah);
db.collection('ak_test_score', function(err, collection) {
collection.find({'std_id':id_school}).toArray(function(err, level) {
res.send(level)
});
});
};
I am using this for a website search using post.
app.post('/find', function(req, res) {
var id_school = req.body.std_id;
console.log('show '+ id_sekolah);
db.collection('ak_test_score', function(err, collection) {
collection.find({'std_id':id_school}).toArray(function(err, level) {
var a = level.std_id;
app.set('data',a);
var b = level.school_name;
});
});
res.redirect('/test_score'); ///// send to my page to get
};
app.get('/test_score', function(req, res) {
var id_school = app.get('data');
console.log('show '+ id_sekolah);
db.collection('ak_test_score', function(err, collection) {
collection.find({'std_id':id_school}).toArray(function(err, level) {
res.send(level)
});
});
};

Resources