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:
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);
});