I have a simple Node/Express app and am trying to pass data from a javascript function to a template (powered by jade).
The javascript function looks like this:
module.exports = {
getFeatures: function() {
var request = require("request")
// ID of the Google Spreadsheet + Base URL
var spreadsheetID = "abcdefg-123456";
var sheetID = "od6";
var url = "https://spreadsheets.google.com/feeds/list/" + spreadsheetID + "/" + sheetID + "/public/values?alt=json";
//empty array for features
var features = [];
//get the features
request({
url: url,
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
var data = body.feed.entry;
data.forEach(function(item) {
var obj = {
pub: item.gsx$publication.$t,
date: item.gsx$date.$t,
title: item.gsx$title.$t,
url: item.gsx$url.$t,
}
features.push(obj);
});
console.log("features", features"); //prints array containing all objects to server console
return features;
}
});
}
};
And the main app looks like this:
'use strict';
var express = require('express');
var jade = require('jade');
var gsheets = require("./gsheets.js"); //pulls in module.exports from above
var featuresOld = require('../private/features.json'); //original json pull (a single array of objects)
var port = process.env.PORT || 3000;
var app = express();
// defining middleweare
app.use('/static', express.static(__dirname + '../../public'));
app.set('view engine', 'jade');
app.set('views', __dirname + '/templates');
...
// features route
app.get('/features', function(req, res) {
var path = req.path;
res.locals.path = path;
var features = gsheets.getFeatures(); //attempting to call js function above
res.render('features', {features: features}); //trying to pass data into a template
});
The first function successfully prints an array of objects to the server console, so I think the error lies in how I'm calling it in the main app.js. (Please note, it's only printing when I have it entered as gsheets.getFeatures();, not var features = gsheets.getFeatures();.)
Please also note that the featuresOld variable is an array of objects that has been successfully passed through to a jade tempalte, so the error is not in the res.render('features', {features: features}); line.
I'm sure this is pretty straightforward, but I can't seem to figure it out. Any help is greatly appreciated, thank you.
I'd recommend you to look into Promises (either Native or using a library like Bluebird).
But without using Promises or generators and keeping things simple, you can pass a callback function that will be called only when the values are retrieved. Within this function you can render the template.
(Note that your function currently does not return anything)
module.exports = {
getFeatures: function(callback) {
var request = require("request")
// ID of the Google Spreadsheet + Base URL
var spreadsheetID = "abcdefg-123456";
var sheetID = "od6";
var url = "https://spreadsheets.google.com/feeds/list/" + spreadsheetID + "/" + sheetID + "/public/values?alt=json";
//empty array for features
var features = [];
//get the features
request({
url: url,
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
var data = body.feed.entry;
data.forEach(function(item) {
var obj = {
pub: item.gsx$publication.$t,
date: item.gsx$date.$t,
title: item.gsx$title.$t,
url: item.gsx$url.$t,
}
features.push(obj);
});
console.log("features", features"); //prints array containing all objects to server console
callback(features); // call the rendering function once the values are available
}
});
}
};
Now in your main app, you just pass a callback to the function
app.get('/features', function(req, res) {
var path = req.path;
res.locals.path = path;
gsheets.getFeatures(function(features) {
res.render('features', {features: features}); //trying to pass data into a template
});
});
Basically, your request function is asynchronous - the request will run in background and the callback function will be called with the value once it's retrieved. In the meantime, the rest of the code will keep running (in your case you'd try to use the value even though it hasn't been retrieved yet).
If you need to do something that depends on that value, then you'd have to put that code in a callback function which would be called when the value is available (as showed above).
Promises provide a nice API for doing that. There are also new features ES6 that helps you better organise asynchronous code.
Related
I am trying to retrieve a var hash to use it in another module. But I have a problem with callback. I have the error "callback is not a function". I use callback because my variable hash is undefined, so I guess it's a problem of asynchronous.
hash.js
var fs = require('fs');
var crypto = require('crypto');
var algorithm = 'sha256';
var hash = function(filename, callback){
var shasum = crypto.createHash(algorithm);
var s = fs.ReadStream(filename);
s.on('data', function(data) {
shasum.update(data)
})
s.on('end', function() {
var hash = shasum.digest('hex')
callback(hash);
})
}
exports.hash = hash;
app.js
app.post('/upload', upload.single('userfile'), function(req, res){
res.cookie('filename', req.file.originalname);
res.cookie('filesize', req.file.size);
var filename = __dirname +'/'+ req.file.path;
console.log(hash.hash(filename))
//res.cookie('hash', hash.hash(filename));
res.redirect('/hash')
})
Your hash() function is defined with this signature:
var hash = function(filename, callback)
That means you need to pass it BOTH a filename and a callback. But, you are passing it only a filename:
console.log(hash.hash(filename))
As you can see in the implementation, the callback is not optional and is the only way to actually get the result like this:
hash.hash(filename, function(result) {
console.log(result);
});
I am a CS student with a strong Java background, and javascript is resulting to be a challenging but fun experience so far, that is until I ran into the situation where I tried to use my own modules to return values that require the program to wait for the completion of some procedure before returning.So far no-one from any forum that I have posted this question on has been able to give an actual code fix to the problem, they have referred me to read further material that is not related to the problem at hand. Would anyone please read the code and provide a working correct standard solution to the problem I am facing?
Here is the code, a simple nodes server application, app.js and a weather module, weatherApp.js that uses an user provided zip code and returns a weather forecast in the area.
here is the code:
weatherApp.js
// The required modules.
var http = require("http");
var https = require("https");
//result object
var resultSet = {
googleRequestUrl:"",
forecastIOrequest:"",
latitude :"",
longitude:"",
localInfo:"",
weather:"",
humidity:"",
pressure:"",
time:""
};
//print out error messages
function printError(error){
console.error(error.message);
}
//Forecast API required information:
//key for the forecast IO app
var forecast_IO_Key = "bb9aac7c57877f8f5fab339e3b55669a";
var forecast_IO_Web_Adress = "https://api.forecast.io/forecast/";
//Create Forecast request string function
function createForecastRequest(latitude, longitude){
var request = forecast_IO_Web_Adress + forecast_IO_Key + "/"
+ latitude +"," + longitude;
return request;
}
//Google GEO API required information:
//Create Google Geo Request
var google_GEO_Web_Adress = "https://maps.googleapis.com/maps/api/geocode/json?address=";
function createGoogleGeoMapRequest(zipCode){
var request = google_GEO_Web_Adress+zipCode + "&sensor=false";
return request;
}
// 1- Need to request google for geo locations using a given zip
function connectToGoogleGEO(zipCode, afterCallback){
var googleRequest = https.get(createGoogleGeoMapRequest(zipCode), function(response){
//saving the Google request URL
resultSet.googleRequestUrl = createGoogleGeoMapRequest(zipCode);
var body = "";
var status = response.statusCode;
//a- Read the data.
response.on("data", function(chunk){
body+=chunk;
});
//b- Parse the data.
response.on("end", function(){
if(status === 200){
try{
var googleReport = JSON.parse(body);
resultSet.latitude = googleReport.results[0].geometry.location.lat;
resultSet.longitude = googleReport.results[0].geometry.location.lng;
resultSet.localInfo = googleReport.results[0].address_components[0].long_name + ", " +
googleReport.results[0].address_components[1].long_name + ", " +
googleReport.results[0].address_components[2].long_name + ", " +
googleReport.results[0].address_components[3].long_name + ". ";
// callback to forecast IO.
afterCallback(resultSet.latitude, resultSet.longitude);
}catch(error){
printError(error.message);
}finally{
// nothing here
}
}else{
printError({message: "Error with GEO API"+http.STATUS_CODES[response.statusCode]})
}
});
});
}
function connectToForecastIO(latitude,longitude){
var forecastRequest = https.get(createForecastRequest(latitude,longitude),function(response){
resultSet.forecastIOrequest = createForecastRequest(latitude,longitude);
var body = "";
var status = response.statusCode;
//read the data
response.on("data", function(chunk){
body+=chunk;
});
//parse the data
response.on("end", function(){
try{
var weatherReport = JSON.parse(body);
resultSet.weather = weatherReport.currently.summary;
resultSet.humidity = weatherReport.currently.humidity;
resultSet.temperature = weatherReport.currently.temperature;
resultSet.pressure = weatherReport.currently.pressure;
resultSet.time = weatherReport.currently.time;
}catch(error){
printError(error.message);
}finally{
console.log(resultSet);
}
});
});
}
function get(zipCode){
var results = connectToGoogleGEO(zipCode, connectToForecastIO);
return results;
}
//define the name of the outer module.
module.exports.get = get;
And here is the server code:
app.js
var express = require("express");
var weatherApp = require("./weatherApp.js");
var path = require("path");
var http = require("http");
var app = express();
//creating routes
//The home
app.get("/", function(req, res){
res.redirect("/weather");
});
app.get("/weather", function(req, res){
res.sendFile(path.join(__dirname + "/index.html"));
});
//------------------------------------------------------
//The resources, css, web js files, images etc.
app.get("/StyleSheets/style.css", function(req, res){
res.sendFile(path.join(__dirname + "/StyleSheets/style.css"));
});
app.get("/webScripts/app.js", function(req, res){
res.sendFile(path.join(__dirname + "/webScripts/app.js"));
});
app.get("/webImages/swirl_pattern.png", function(req, res){
res.sendFile(path.join(__dirname + "/webImages/swirl_pattern.png"));
});
//-------------------------------------------------------
//other requests
app.get("/zipcode.do", function(req, res){
var zipcode = req.query["zipcode"];
var response = "No report Available";
function getReport(zipCode, callback){
response = weatherApp.get(req.query["zipcode"]);
}
getReport(zipcode, ()=>{
res.send("<p>" + response+ "</p>");
});
});
//any other entry thats not listed as a valid to request
app.get("/:title", function(req,res){
var title = req.param.title;
if(title === undefined){
var status = res.status(503);
res.send("This page does not exists" + '"' + http.STATUS_CODES[503] + '"');
}else{
res.send(title);
}
});
app.listen(3000, function(){
console.log("Server running at port: 3000")
});
The main issue I am having right now is:
The program is not returning anything from the module even when final console.log in the weather module prints the right resultSet object.
The server is not waiting for the module to return, and continues to print no data.
Can someone provide a working fix to any of these problems I would be really grateful, This has really hindered my progress and broken down my morale a little :(
Your problem is that you are using asynchronous functions as if they were synchronous.
It might not be the only issue here, but this function is particularly problematic:
function get(zipCode){
var results = connectToGoogleGEO(zipCode, connectToForecastIO);
return results;
}
connectToGoogleGEO() calls the asynchronous https.get() function and does not return the data that is retrieved from Google. You need to rewrite your code so that it does not expect the data to be returned by the function. Instead, you need to pass a callback that will handle the data.
Take care to know when you are calling asynchronous functions and how their callbacks work. It is fundamental when working with Node.js
My controller is a soap client as shown below
var _ = require('lodash'),
memoize = require('memoize'),
soap = require('soap'),
http = require('http');
var wsdlUrl = 'http://www.proxixnetwork.com/gsert/PxPointGeocode.asmx?WSDL';
var geocode = function(req,res){
var sessionId = null;
soap.createClient(wsdlUrl, function(err,client){
var args = {"username":'user123', "password":'password123'};
client.PxPointGeocode.PxPointGeocodeSoap.Authenticate(args,function(err,result){
res.jsonp(result.AuthenticateResult.SessionID);
})
});
}
exports.authenticate = geocode;
This soap service provides a session id that will be used in subsequent requests. Hence, I wanted to use 'memoize' to cache the method.
I defined a method that wraps around the soap call and 'memoize'ing it but the problem is that the call to soapClient is not happening.
I do not know how to make the call from router wait for the soap call
Note: Also tried async library's waterfall but did not work.
var _ = require('lodash'),
memoize = require('memoize'),
soap = require('soap'),
http = require('http'),
wsdlUrl = 'http://www.proxixnetwork.com/gsert/PxPointGeocode.asmx?WSDL';
var getSession = function () {
var args = {"username": 'user123', "password": 'password123'};
var sessionId = null;
soap.createClient(wsdlUrl, function (err, client) {
console.log('Inside proxix client'); //not printing
client.PxPointGeocode.PxPointGeocodeSoap.Authenticate(args, function (err, result) {
sessionId= result.AuthenticateResult.SessionID;
//if I use res.jsonp() - the call could be made
})
});
return sessionId;
};
var cached = memoize(getSession);
var geocode = function (req, res) {
var sesssionObj = cached();
res.jsonp(sessionObj);
}
exports.authenticate = geocode;
The two issues I'm seeing are :
memoize is a caching mechanism but is in no way going to change the ansynchronous nature of your function. Returning sessionId will not work because it is not set before it gets to that line. You need to use a callback.
You have not specified which arguments you want memoize to use for the caching, nor where you are getting those values. I'm going to assume for this example that it's username, password and wsdlUrl. and that they are set directly in req.
.
var _ = require('lodash'),
memoize = require('memoize'),
soap = require('soap'),
http = require('http');
var getSessionId = function(username,password,wsdlUrl,callback){
soap.createClient(wsdlUrl, function(err,client){
var args = {"username":username, "password":password};
client.PxPointGeocode.PxPointGeocodeSoap.Authenticate(args,function(err,result){
if(err){
return callback(err);
}
callback(null,result.AuthenticateResult.SessionID);
})
});
});
var getSessionIdCached = memoize(getSessionId,{async:true});
var geocode = function(req,res){
getSessionIdCached(req.username,req.password,req.wsdlUrl,function(err,sessionId){
if(err){
//do some error handling, and probably return
}
res.jsonp(sessionId);
});
});
exports.authenticate = geocode;
I am a beginner at node js and I'm trying to write a web scraping script. I got permission from the site admin to scrape their products if I make less then 15 requests a minute. When I started out it used to request all the URLs at once but after some tooling around, I was able to go through each item in the array, but the script doesn't stop when there is no more items in the array? I'm not really happy with my result and feel like there is a better way to do this.
var express = require('express');
var fs = require('fs');
var request = require('request');
var cheerio = require('cheerio');
var app = express();
var async = require('async');
app.get('/scrape', function(req, res){
productListing = ['ohio-precious-metals-1-ounce-silver-bar','morgan-1-ounce-silver-bar']
var i = 0;
async.eachLimit(productListing, 1, function (product, callback) {
var getProducts = function () {
var url = 'http://cbmint.com/' + productListing[i];
request(url, function(error, response, html) {
if(!error){
var $ = cheerio.load(html);
var title;
var json = { title : ""};
$('.product-name').filter(function(){
var data = $(this);
title = data.children().children().first().text();
json.title = title;
})
}
var theTime = new Date().getTime();
console.log(i);
console.log(json.title);
console.log(theTime);
i++;
});
}
setInterval(getProducts,10000);
})
res.send('Check your console!')
})
app.listen('8081')
console.log('Magic happens on port 8081');
exports = module.exports = app;
You aren't calling callback inside the iterator function. Take a look at the docs for eachLimit.
I'm going through a Node, Express, & Socket.io chat tutorial. I decided to use Redis to store the chat history and have successfully set it up so that my information is correctly posting to the database. I am now trying to access that information to use on the client-side (in this case I'm trying to access the list of users currently in the chat so I can show them to the side of the chat). I am using $.getJSON to make a GET request. Right now I have it setup so that the file it tries to access only has this JSON object : {"dog" : "2","cat":"3"} just to test it, and that is working, but I'm not sure where to go from there because anytime I try adding a function into that file, even if I specify to return a JSON object and call that function, the request stops returning the correct information.
For example I tried :
var data = function(){
return {"dog" : "2","cat":"3"}
}
data();
and that doesn't return anything ( I understand that when I make a GET request the function isn't run, but it doesn't even return that text, and if it doesn't run a function than I'm not sure how I can access redis from this file)
Here's what I'm thinking:
var redis = require('redis')
//figure out how to access the redis client that I have at localhost:6379, something like var db = redis.X
//and then call (for example) db.smembers('onlineUsers') and be returned the object which I can iterate through
Here's my relevant code:
server.js:
var jade = require('jade');
var PORT = 8080;
var redis = require('redis');
var db = redis.createClient();
var pub = redis.createClient();
var sub = redis.createClient();
var http = require('http');
var express = require('express');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
server.listen(PORT, function(){
console.log("Now connected on localhost:" + PORT)
});
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.set("view options", {layout: false});
app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res){
res.render('home');
});
io.sockets.on('connection', function(client){
sub.subscribe("chatting");
sub.on("message", function (channel, message) {
console.log("message received on server from publish");
client.send(message);
});
client.on("sendMessage", function(msg) {
pub.publish("chatting",msg);
});
client.on("setUsername", function(user){
pub.publish("chatting","A new user in connected:" + user);
db.sadd("onlineUsers",user);
}
);
client.on('disconnect', function () {
sub.quit();
pub.publish("chatting","User is disconnected :" + client.id);
});
});
script.js:
$(document).ready( function(){
$client = io.connect();
initialize();
});
var setUsername = function(){
var username = $("#usernameInput").val();
if (username)
{
var user = username;
$client.emit('setUsername', username);
$('#chatControls').show();
$('#usernameInput').hide();
$('#usernameSet').hide();
showCurrentUsers();
}
}
var showCurrentUsers = function(){
$('#list_of_users').empty();
$.getJSON('getusers.js', function(data){
for (var i = 0; i < data.length; i++){
$('list_of_users').append("<li>"+data[i]+"</li>")
}
})
}
var sendMessage = function(){
var msg = $('#messageInput').val();
var username = $("#usernameInput").val();
if (msg)
{
var data = {msg: msg, user: username}
$client.emit('message', data);
addMessage(data);
$('#messageInput').val('');
// populate(username,msg);
}
}
var addMessage = function(data) {
$("#chatEntries").append('<div class="message"><p>' + data.user + ' : ' + data.msg + '</p></div>');
}
// var populate = function(username,msg) {
// var data ;
// }
var initialize = function(){
$("#chatControls").hide();
$("#usernameSet").on('click', setUsername);
$("#submit").on('click',sendMessage);
showCurrentUsers();
}
and right now all that the getusers.js file has in it is:
{"dog" : "2","cat":"3"}
It looks like you're expecting your call to $.getJSON to load and execute the javascript it loads. It doesn't work this way. You need to make a node endpoint (via a route) which renders the JSON. The node endpoint would then do the data manipulation / querying redis:
Node:
In routes.js:
app.get('/chatdata', ChatController.getChatData);
In ChatController.js (manipulate, create the data as you like here)
exports.getChatData = function (req, res) {
var data = function(){
return {"dog" : "2","cat":"3"}
};
res.JSON(data);
};
Front-end
$.getJSON('getChatData', function(data){
//...
})
I think you need to setup a route to handle the GET request that $.getJSON makes, or if getusers.js is in the /public directory, then you need to modify your $.getJSON call as follows:
$.getJSON('http://localhost:8080/public/getusers.js', function(data){
Ok, it looks like it is a problem with your getusers.js file. $.getJSON seems to prefer double quotes. Try formatting it like this:
{
"dog" : "2",
"cat" : "3"
}
Also, try using this to display the data:
$.getJSON('getusers.js', function(data){
var items = [];
$.each( data, function( key, val ) {
items.push("<li id='" + key + "'>" + val +"</li>");
});
$('#list_of_users').append(items.join(""));
});