I have setup a basic Node.js server using express (WebStorm default), and have attempted to make it upon request (from a pebble watch) to run a python script, and send the returned json in the form:
{"willCollide": 1, "time": 6000, "strength": "NA"}
back to the watch. I have just started looking into JavaScript so have very little experience, and would expect I'm doing most of this incorrectly.
Currently I experience an "Error: can't set headers after they are sent" and was wondering what is the correct method to send a json to a user upon a request?
I am also wondering whether this is indeed the best method to go about sending the data from the python script to a Pebble watch.
Below is the code in the JavaScript file being called on the request:
var express = require('express');
var router = express.Router();
var PythonShell = require('python-shell');
var options = {
mode: 'json'
};
var rain_data;
function run_py_script(data){
var pyshell = new PythonShell('dummy.py', options);
var ret_val;
/* Dummy data doesnt matter atm */
pyshell.send("dummy data"); // change to data
pyshell.on('message', function(message){
console.log(message);
ret_val = message;
console.log(message["willCollide"]); // debug check
});
pyshell.end(function(err){
if (err) {
console.log('error received from python script');
}
console.log('finished script');
});
return ret_val;
}
/* GET rain_track data. */
router.get('/', function(req, res, next) {
rain_data = run_py_script(null);
res.write(rain_data);
res.end();
});
module.exports = router;
Seems you're having trouble with asynchronous execution.
Your function run_py_script(data) does not return the final value until the end event is fired. Then, you'll be able to return the response back to the user.
Here you have two possible solutions:
Callbacks
Promises
I'm going to make an approach using a callback
First, run_py_script will have 2 arguments, data and a function to be called with the response, let's call it cb. cb will be called eventually with the final data.
function run_py_script(data, cb) {
// I'm going to summarize this code
var ret_val;
pyshell.on('message', function(message){
ret_val = message;
});
pyshell.end(function(err){
return err ? cb(null) : cb(ret_val);
});
// note there is no return statement
}
Now, let's create your controller:
router.get('/', function(req, res, next) {
run_py_script(null, function(rain_data) {
res.json(rain_data); // same as write().end() but more elegant
});
});
Final bonus: The node convention for cb is to be a 2 arguments function; the first argument uses to be an error which will be null is everything is ok, and the second argument is the data itself which will be null if error.
With this in mind the final code would be (summarizing)
function run_py_script(data, cb) {
// ...
pyshell.end(function(err){
return err ? cb(err, null) : cb(null, ret_val);
});
}
run_py_script(null, function(err, rain_data){
if (err){ return res.json(null); }
return res.json(data);
});
Related
I am new to Nodejs and Express and want to search some results from mongoDB and show it on client browser, i can find the values from the mongoDB query but not able to send it to client js file,
It says doc is not defined, any help will be appreciated.
***app.js(Server)***
var bodyParser = require("body-parser");
var express = require("express");
var app = express();
var port = "8001";
var mongo= require('mongodb');
var mongoClient=mongo.MongoClient;
app.use(bodyParser.json());
app.use(express.static('public'));
app.get('/home', function(req, res) {
res.sendFile(__dirname + "/public/views/index.html");
});
app.listen(port, function() {
console.log("Server running at:" + port);
})
app.post("/response", function(req, res) {
var t = req.body;
mongoClient.connect("mongodb://localhost:27017/query", function(err,db){
cursor =db.collection('response').find({"name1":t.text},{"name2":1, "_id":0});
cursor.each(function(err, doc) {
if (doc != null) {
console.log(doc);
}
});
})
res.send(doc);
});
***index.js(Client Side)***
$.ajax({
url: '/response',
type:"POST",
contentType:"application/json; charset=utf-8",
complete: function(data) {
console.log(data.responseText);
alert(data.responseText);
}
});
doc is a variable local to your closure and therefore not available when you call res.send(doc).
In addition to that, you are iterating over all of your documents. You need to choose which one to return.
I recommend something like this:
cursor = db.collection('response').find({"name1":t.text},{"name2":1, "_id":0});
cursor.each(function(err, doc) {
if (doc != null) {
console.log(doc);
return res.json(doc); // return the first document found
}
});
Also note:
you should sanitize your data before passing it into the query
you shouldn't connect to the database on each request, instead set up mongo in the application context
you should check err to see if mongo returned an error before trying to iterate the cursor
EDIT:
To be more specific, the whole block should look like this:
app.post("/response", function (req, res) {
var t = req.body;
mongoClient.connect("mongodb://localhost:27017/query", function (err, db) {
if (err) {
return res.json(err);
}
db.collection('tweets').findOne({"name1": t.text}, {"name2": 1, "_id": 0}, function (err, doc) {
if (doc != null) {
console.log(doc);
return res.json(doc);
}
return res.sendStatus(404);
});
});
});
A few things:
cursor.each() has been deprecated in favour of cursor.forEach() assuming you're running a recent version of mongo
Your first line in a callback should be something like if (err) { console.error(err) } - at this point you'd probably see that your query is invalid:
Your query should probably look like .find({'name1': t.text, 'name2': 1, '_id': 0})
If you are referencing an auto-generated mongo ObjectID as _id, you have to use '_id': new mongo.ObjectID(<whatever string holds the _id>) in order for it to work. If you didn't specify an _id on creation, the automatically generated ObjectID will require this.
The mongo docs are great, highly recommend leafing through them for examples and which bits take which arguments and the options for all of them.
Consider using promises instead of callbacks to help tidy up. It's really easy with mongo - you just don't specify a callback function, and instead tack a .then(document => { ... }) on the end, and a single .catch(err => {console.error(err)}) will catch errors at the db, collection and cursor levels.
With jQuery, consider using .done(result => {...}) and .fail(err => { ... }) (aka promises) instead of complete for your ajax calls, and whatever you do, don't forget to attach an error handler. (I'm not even sure 'complete' is the right property, might depend on which jQuery you're using)
If you're doing an AJAX POST you should probably attach some data (and a dataType)to it. Otherwise you'll still get no records because req.body will be undefined or empty.
In the case of an error, you should be responding with res.status(500); res.end() at a minimum so you can tell when something has gone wrong on the server end.
To help along, console.log(req.body) right at the top of your function so you know what data is arriving.
Finally, if you intend on responding with JSON - use res.json(doc) instead of res.send(doc)
What is the best practice to send two independed MongoDB results in Express application via HTTP Method?
Here is a short example which makes it clear:
//app.js
var express = require('express');
var app = express();
var testController = require('./controllers/test');
app.get('/test', testController.getCounts);
...
Following getCounts() function wouldn't work because I can't send the response twice.
///controllers/test
exports.getCounts = function(req,res) {
Object1.count({},function(err,count){
res.send({count:count});
});
Object2.count({},function(err,count){
res.send({count:count});
});
};
Anyway, I would like to have those two counts in one response object.
Should I call Object2.count in the callback of Object1 even if they are not dependent to each other?
Or should I re-design it somehow else?
Thank you!
You should use Promise to achieve this task :
function getCount(obj) {
return new Promise(function (resolve, reject) {
obj.count({}, function(err,count) {
if(err) reject();
else resolve(count);
});
});
}
With Promise.all you can trigger the two request and retrieve the results in order to add it to the response
exports.getCounts = function(req,res) {
Promise.all([getCount(Object1), getCount(Object2)])
.then(function success(result) {
res.send({'count1':result[0], 'count2':result[1]});
});
});
When you call res.send you will end the response for the request. You could instead use res.write, which will send a chunk to the client, and when done call res.end;
Example:
app.get('/endpoint', function(req, res) {
res.write('Hello');
res.write('World');
res.end();
});
However, it seems like you are trying to send json back to the client which raises and problem: writing to object separately will not be valid json.
Example:
app.get('/endpoint', function(req, res) {
res.write({foo:'bar'});
res.write({hello:'world'});
res.end();
});
The response body will now be: {foo:'bar'}{hello:'world'} which is not valid json.
There will also be a race condition between the two db queries, which means that you are not certain about the order of the data in the response.
Suggestion:
exports.getCounts = function(req,res) {
var output = {};
Object1.count({},function(err,count){
output.count1 = count;
Object2.count({},function(err,count){
output.count2 = count;
res.send(output);
});
});
};
//Response body
{
count1: [value],
count2: [value]
}
var database = require('database');
var express = require('express');
var app = express();
var cors = require('cors');
app.use(cors());
var bodyParser = require('body-parser');
var urlencodedParser = bodyParser.urlencoded({
extended: false
});
app.post('/dosomething', urlencodedParser, function(req, res) {
if (!req.body.a) {
res.status(500).send(JSON.stringify({
error: 'a not defined'
}));
return;
}
firstAsyncFunction(req.body.a, function(err, result) {
if (err) {
res.status(500).send('firstAsyncFunction was NOT a success!');
} else {
if (result.b) {
secondAsyncFunction(result.b, function(err, data) {
if (err) {
res.status(500).send('secondAsyncFunction was NOT a success!');
return;
}
res.send('EVERYTHING WAS A SUCCESS! ' + data);
});
}
else {
res.status(500).send('result.b is not defined');
}
}
});
});
function firstAsyncFunction(param, callback) {
//Some network call:
// Return either return (callback(null,'success')); or return (callback('error'));
var query = database.createQuery(someOptionsHere);
database.runDatabaseQuery(query, function(err, entities, info) {
if (err) {
return (callback('error'));
}
return (callback(null, 'success'));
});
};
function secondAsyncFunction(param, callback) {
//Some network call:
// Return either return (callback(null,'success')); or return (callback('error'));
var query = database.createQuery(someOptionsHere);
database.runDatabaseQuery(query, function(err, entities, info) {
if (err) {
return (callback('error'));
}
return (callback(null, 'success'));
});
};
var server = app.listen(process.env.PORT || 3000, function() {
var host = server.address().address;
var port = server.address().port;
console.log('App listening at http://%s:%s', host, port);
});
module.exports = app;
I have here a basic express http server. This server has one route, dosomething, which makes two network calls and tells the user if they were a success or not.
This is my entire webserver (this is a bare bones server of my actual server for example purposes). I am now concerned with this server crashing. Reading the docs for express I see there is a default error handler which will catch errors and prevent the server from crashing (http://expressjs.com/en/guide/error-handling.html). I have added the code:
function defaultErrorHandler(err, req, res, next) {
if (res.headersSent) {
return next(err);
}
res.status(500);
res.render('error', { error: err });
}
app.use(defaultErrorHandler);
This still crashes my server though. For example. I had a problem with my database returning an improper JSON response and inside of my firstAsyncFunction (not shown in the code) I tried to parse the JSON and it caused an error telling me it was improper JSON and the server crashed and was unable to take requests anymore until I restarted it. I would like to avoid this and have the default error handler send out a generic response back to the user when this occurs. I thought if I specified the defaultErrorHandler and put it inside of app.use that it would capture and handle all errors, but this does not seem to be the case? Inside of my async function for example you can see I am looking if an error was returned and if it was I send an error back to the user, but what if some other error occurs, how can I get express to capture and handle this error for me?
The defaultErrorHandler cannot handle exceptions that are thrown inside asynchronous tasks, such as callbacks.
If you define a route like:
app.get('/a', function(req, res) {
throw new Error('Test');
});
An error will be thrown, and in this case defaultErrorHandler will successfully catch it.
If the same exception occurs in an async manner, like so:
app.get('/a', function(req, res) {
setTimeout(function () {
throw new Error('Test');
}, 1000);
});
The server will crush, because the callback is actually in another context, and exceptions thrown by it will now be caught by the original catcher. This is a very difficult issue to deal with when it comes to callback.
There is more than one solution though. A possible solution will be to wrap every function that is prone to throw error with a try catch statement. This is a bit excessive though.
For example:
app.get('/a', function(req, res) {
setTimeout(function () {
try {
var x = JSON.parse('{');
}
catch (err) {
res.send(err.message);
}
}, 1000);
});
A nicer solution:
A nicer solution, would be to use promises instead, if it's possible, then for example you can declare a single errorHandler function like so:
function errorHandler(error, res) {
res.send(error.message);
}
Then, let's say you have to following function with fetches stuff from the database (I used setTimeout to simulate async behavior):
function getStuffFromDb() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("{");
}, 100);
});
}
Notice that this function returns an invalid JSON string. Your route will look something like:
app.get('/a', function(req, res) {
getStuffFromDb()
.then(handleStuffFromDb)
.catch(function (error) { errorHandler(error, res) });
});
function handleStuffFromDb(str) {
return JSON.parse(str);
}
This is a very simplified example, but you can add a lot more functionality to it, and (at least theoretically) have a single catch statement which will prevent your server from crushing.
I am trying to pass some data from my db to the router which then passes the data to the view.
My model code :
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'test'
});
var result; // empty var which should later be filled with the querys result
connection.connect();
var query = connection.query('SELECT * FROM users', function(err, res, fields) {
if (err) throw err;
result = res; // overwrite result with the querys result
console.log(res); // This prints out everything I need
});
module.exports = {
data: result // should contain the query result (= 2 objects in this case)
}
Now to my route file :
var express = require('express');
var router = express.Router();
var Users = require('../models/users');
console.log(Users.data);
/* GET home page. */
router.get('/users', function(req, res) {
res.render('api', { data: Users.data, title: "Test API Output" });
});
module.exports = router;
When I console.log Users or Users.data I get undefined. I don't really get why this is the case. How else am I supposed to pass data along the files.
All help is gladly read :) Thank you.
module.exports are being evaluated the second you require and variables are not passed by reference in this case.
What that means for your code is the following:
var result; // result is "undefined" because it does not contain a value here
// You are doing your DB queries here...
module.exports = {
data: result // ...and because the query has not finished here yet, result
// is still undefined.
// This is also a good example of a so called "race condition", because there is a
// slight (improbable) chance that the query might have already finished.
// Hence, it could happen that sometimes result is already filled.
}
When you now require the above file in another file of your code, the above is being evaluated and saved straight away (result is undefined at that point in time, hence it is also undefined when it exports).
Your query is being executed and written into the result variable, but at that point in time you can not modify the exported variable anymore – because it is it's own variable and not merely a reference to result).
What you could do is the following:
function getData(callback) {
connection.query('SELECT * FROM users', function(err, res, fields) {
callback(err, res);
});
}
module.exports = {
getData: getData
}
and then in your other file:
var Users = require('../models/users');
Users.getData(function(err, result) {
// TODO: Error handling.
console.log(result);
});
That's exactly why it's so easy with JavaScript to end up in callback hell, because of it's asynchronous nature.
The above is the exact same situation as if you, f.e., want to get some data via AJAX from a server and then fill tables with it. When you start creating the table before you have the data (so the AJAX request is not yet complete), you end up with an empty table. What could do is:
you create a variable that holds your data and
a function that creates the table
when you then ask the server for the data (via AJAX) you wait until you get the data (completion callback) and only then you start creating the table: filling your variable and calling the function to fill the table with the data.
Server-Side JavaScript is the same as client-side. Never forget this.
As a little homework for the reader: the way to get out of callback hell is by reading up on promises – a pattern/architecture which reduces indents and saves lots of headaches :)
(update: Lucas' answer is basically telling the same thing as I did)
(update 2: wrong way of handling err)
I suggest realize the consult in the route file, some like this:
var express = require('express');
var router = express.Router();
var Users = require('../models/users');
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'test'
});
var result; // empty var which should later be filled with the querys result
connection.connect();
/* GET home page. */
router.get('/users', function(req, res) {
var query = connection.query('SELECT * FROM users', function(err, res, fields) {
if (err) throw err;
result = res; // overwrite result with the querys result
res.render('api', { data: res.data, title: "Test API Output" });
});
});
module.exports = router;
But you can configure the connection with database in another file, in libs/mysql_connect.js.
The undefined is caused because the response of connection.query don't works out of the connection.query.
If you really want the query to run only once and then just re-use the already queried data, I think you are after something like this for your model:
...
var data;
var mymodel = {};
...
mymodel.getData = function(callback) {
if(data) {
callback(data);
} else {
db.query('select * from users', function(err,res,fields) {
// error checking and such
data = res;
callback(data);
});
}
}
module.exports = mymodel
In your router, you'd then use it like this:
...
router.get('/users', function(req, res) {
Users.getData(function(mydata) {
res.render('api', { data: mydata, title: "Test API Output" });
});
});
The first time you call getData, you'll get a fresh result, and on subsequent calls you get the cached result.
While you could expose data in mymodel directly, and only use the callback in case it is still undefined, that'd make your code in the router more convulated.
I've built a series of database queries in my express app that reside in a /models/index.js file which I can access from app.js via var express = require('express');. I am trying to populate req.session.user with a userid that is returned by a findByEmail(); function in /models/index.js.
The findByEmail(); function works fine, however I can't figure out how to store its return value in req.session. I've tried including req.session.id = result.rows[0].id; in the 'findByEmail();function, but this returns areq is not defined` error.
Am I overlooking a simple require statement in my /models/index.js file or is there another trick to accessing req.session in a module?
I've included the relevant code from /models.index.js below:
/models.index.js:
var pg = require('pg');
function findByEmail(email){
pg.connect(function(err, client, done) {
if(err) {
console.log('pg.connect error');
throw err;
}
client.query('BEGIN', function(err) {
if(err) {
console.log('client.query BEGIN error');
return rollback(client, done);
}
process.nextTick(function() {
var text = "SELECT * FROM users WHERE email = $1";
client.query(text, [email], function(err, result) {
if(err) {
console.log(err);
return rollback(client, done);
}
console.log(result);
console.log(result.rows);
console.log('id: ', result.rows[0].id);
req.session.id = result.rows[0].id;
done();
});
});
});
});
}
module.exports.pg = pg;
exports.findByEmail = findByEmail;
As far as /models/index.js knows, req is not defined, same thing with rollback. A module is a closure and you don't have access to variables defined outside of it.
If you want to do that you must pass them as parameters but it's not very good design, as #gustavohenke said: Separation of concerns.
You might want to have a callback and call it with success/error and set the session id there so you don't have to pass in into the module:
function findByEmail(email,callback){
pg.connect(function(err, client, done) {
if(err) {
console.log('pg.connect error');
throw err;
}
// Do all the async work and when you are done ...
// An error is usually passed as the first parameter of the callback
callback(err,result)
});
}
exports.findByEmail = findByEmail;
You would then call it like this:
var models = require('./models');
models.findByEmail('thedude#lebowski.com',function(err,results) {
// set session id here where you probably have access to the req object...
})