How to export a variable from a scheduled function - javascript

I have a function that gets input constantly but then only processes it every minute with a cron job.
The most recent output should be stored in a variable and retrieved from the outside at random times.
Here in a very simplified form:
let input = 'something';
let data = '';
data += input;
require('node-schedule').scheduleJob('* * * * *', somethingMore);
function somethingMore() {
let output = data += 'More';
// return output;
}
console.log(output);
Initializing the variable outside the function like above doesn't seem to work in this case.
Calling the function directly or assigning it to a variable doesn't help, as it would run it before it's due.
I also tried with buffers, but they don't seem to work either, unless I missed something.
The only thing that does work is writing a file to disk with fs and then reading from there, but I guess it's not the best of solutions.

It seems like you just let your chron function run as scheduled and you save the latest result in a module-scoped variable. Then, create another exported function that anyone else can call to get the latest result.
You're only showing pseudo-code (not your real code) so it is not clear exactly what you want to save for future inquiries to return. You will have to implement that part yourself.
So, if you just wanted to save the most recent value:
// module-scoped variable to save recent data
// you may want to call your function to initialize it when
// the module loads, otherwise it may be undefined for a little bit
// of time
let lastData;
require('node-schedule').scheduleJob('* * * * * *', () => {
// do something that gets someData
lastData = someData;
});
// let outside caller get the most recent data
module.exports.getLastData = function() {
return lastData;
}

Related

How to store reference to function?

Given a list of functions, I wish the user to be able to select any of the functions to run at startup. How can this be done so that the user can "save" their choice of function to run the next time the code is run ie what would function runSelectedFunction (below) look like since you can't "save" a javascript function to file? Also, assume the list of potential functions is extensible.
const first = ()=>{
console.log('first');
}
const second = ()=>{
console.log('second');
}
const third = ()=>{
console.log('third');
}
loadUserSelectedFunctionFromDB()
.then(runSelectedFunction)
To be clear, the goal is to persist the user choice even if the code stops executing and is restarted. Normally, this would be done by storing a value in a database but the question is how to store a reference to a function in a database given an extensible set of functions?
Use a map like this:
const m = {
first, second, third
};
let selectFuncName = "first"; // from user selection, maybe click a button
let selectFunc = m[selectFuncName];
loadUserSelectedFunctionFromDB()
.then(runSelectedFunction)

Firebase functions - adding new value to the oldvalue

I'm creating an android app that logs how long a person spends on certain things. I want to add the time spent to the total time spend, so I know how long a user has spent on an exercise type
I want to do it in a function, since I think it's easier than transactions.
exports.addExerciseTime = functions.database.ref('users/{userid}/stats/exerciseTime/{type}').onWrite( event =>{
console.log("Exercise time updates...");
var newdata = event.data.val();
var oldData = event.data.previous.val();
return event.data.ref.update(oldData+ newdata);
});
Now, I know that this function will loop until firebase shuts it down.
But how would I do this? Is there an easier way to do this?
you have an easy option of adding a flag indicating that you updated the data. next time you will get into the function, just start by checking if the flag exists in if so, exit the function. the con of this one is that you will run the function at least n+1
another option, according to their latest post, you know have a "onUpdate" and "onCreate" triggers as well. you might be able to use them smartly to optimize this even more (for example: only on first creation do XYZ, so it won't run on each update).
https://firebase.googleblog.com/2017/07/cloud-functions-realtime-database.html
Like you are saying, onWrite will capture every writing event. My solution would be replacing onWrite with onCreate, however let the user write to another path because Firebase will keep triggering the function. Besides that, your approach this is not the best solution since the updates can conflict. The use of transactions is better. That would look like this:
exports.addExerciseTime = functions.database.ref('users/{userid}/stats/exerciseTime/{type}').onCreate( event =>{
console.log("Exercise time updates...");
var newdata = event.data.val();
const pathToValue = //create the path here to exercisetime
return pathToValue.transaction(function(exercisetime) {
return (exercisetime || 0) + newdata;
});
});
*Notice the onCreate event instead of onWrite. Again: You will need to write it to a other path.

node.js setInterval not working in custom module

I am developing a web application in node.js to collect data from devices on a network using snmp. This is my first real encounter with node.js and javascript. In the app each device will be manipulated through a module I named SnmpMonitor.js. This module will maintain basic device data as well as the snmp and database connection.
One of the features of the app is the ability to constantly monitor data from smart metering devices. To do this I created the following code to start and stop the monitoring of the device. It uses setInterval to constantly send a snmp get request to the device. Then the event listener picks it up and will add the collected data to a database. Right now the listener just prints to show it was successful.
var dataOIDs = ["1.3.6.1.2.1.1.1.0","1.3.6.1.2.1.1.2.0"];
var intervalDuration = 500;
var monitorIntervalID;
var dataCollectionEvent = "dataCollectionComplete";
var emitter = events.EventEmitter(); // Uses native Event Module
//...
function startMonitor(){
if(monitorIntervalID !== undefined){
console.log("Device monitor has already started");
} else {
monitorIntervalID = setInterval(getSnmp,intervalDuration,dataOIDs,dataCollectionEvent);
emitter.on(dataCollectionEvent,dataCallback);
}
}
function dataCallback(recievedData){
// receivedData is returned from getSnmp completion event
// TODO put data in database
console.log("Event happened");
}
function stopMonitor(){
if(monitorIntervalID !== undefined){
clearInterval(monitorIntervalID);
emitter.removeListener(dataCollectionEvent,dataCallback);
} else {
console.log("Must start collecting data before it can be stopped");
}
}
//...
I also have a test file, test.js, that requires the module, starts monitoring, waits 10 seconds, then stops it.
var test = require("./SnmpMonitor");
test.startMonitor();
setTimeout(test.stopMonitor,10000);
My problem is that the setInterval function in startMonitor() is not being run. I have tried placing console.log("test"); before, inside, and after it to test it. The inside test output never executes. The monitorIntervalID variable is also returned as undefined. I have tested setInterval(function(){ console.log("test"); },500); in my test.js file and it runs fine with no issues. I feel like this is a noobie mistake but I just can't seem to figure out why it won't execute.
Here is a link to the entire module: SnmpMonitor.js
I not sure exactly what was wrong but I got it to work by overhauling the whole class/module. I thought the way I had it was going to allow me to create new monitors objects but I was wrong. Instead I created two functions inside the monitor file that do the same thing. I changed the start function to the following.
SnmpMonitor.prototype.start = function() {
var snmpSession = new SNMP(this.deviceInfo.ipaddress,this.emitter);
var oids = this.deviceInfo.oids;
var emit = this.emitter;
var duration = this.intervalDuration;
this.intervalID = setInterval(function(){
snmpSession.get(dataCollectionEvent,emit,oids);
},duration);
};
The setInterval function seems to work best when the callback function is set inside an anonymous function, even though technically you can pass it directly. Using the this. notation I created some class/module/function variables (whatever its called in js) that are in scope of the whole class. For some reason the variables accessed through this. do not work so well when directly in a function or expression so I created temp variables for them. In my other version all the variables were global and js doesn't seem to like that.

Extracting values from USGS real time water service

There must be something simple I am missing, but alas, I do not know what I do not know. Below is the code I have thus far for trying to get current streamflow conditions from the USGS.
// create site object
function Site(siteCode) {
this.timeSeriesList = [];
this.siteCode = siteCode;
this.downloadData = downloadData;
this.getCfs = getCfs;
// create reference to the local object for use inside the jquery ajax function below
var self = this;
// create timeSeries object
function TimeSeries(siteCode, variableCode) {
this.variableCode = variableCode;
this.observations = [];
}
// create observation object
function TimeSeriesObservation(stage, timeDate) {
this.stage = stage;
this.timeDate = timeDate;
}
// include the capability to download data automatically
function downloadData() {
// construct the url to get data
// TODO: include the capability to change the date range, currently one week (P1W)
var url = "http://waterservices.usgs.gov/nwis/iv/?format=json&sites=" + this.siteCode + "&period=P1W&parameterCd=00060,00065"
// use jquery getJSON to download the data
$.getJSON(url, function (data) {
// timeSeries is a two item list, one for cfs and the other for feet
// iterate these and create an object for each
$(data.value.timeSeries).each(function () {
// create a timeSeries object
var thisTimeSeries = new TimeSeries(
self.siteCode,
// get the variable code, 65 for ft and 60 for cfs
this.variable.variableCode[0].value
);
// for every observation of the type at this site
$(this.values[0].value).each(function () {
// add the observation to the list
thisTimeSeries.observations.push(new TimeSeriesObservation(
// observation stage or level
this.value,
// observation time
this.dateTime
));
});
// add the timeSeries instance to the object list
self.timeSeriesList.push(thisTimeSeries);
});
});
}
// return serialized array of cfs stage values
function getCfs() {
// iterate timeseries objects
$(self.timeSeriesList).each(function () {
// if the variable code is 00060 - cfs
if (this.variableCode === '00060') {
// return serialized array of stages
return JSON.stringify(this.observations);
}
});
}
}
When I simply access the object directly using the command line, I can access individual observations using:
> var watauga = new Site('03479000')
> watauga.downloadData()
> watauga.timeSeriesList[0].observations[0]
I can even access all the reported values with the timestamps using:
> JSON.stringify(watauga.timeSeriesList[0].observations)
Now I am trying to wrap this logic into the getCfs function, with little success. What am I missing?
I don't see anything in the code above that enforces the data being downloaded. Maybe in whatever execution path you're using to call getCfs() you have a wait or a loop that checks for the download to complete prior to calling getCfs(), but if you're simply calling
site.downloadData();
site.getCfs()
you're almost certainly not finished loading when you call site.getCfs().
You'd need to do invoke a callback from within your success handler to notify the caller that the data is downloaded. For example, change the signature of Site.downloadData to
function downloadData(downloadCallback) {
// ...
Add a call to the downloadCallback after you're finished processing the data:
// After the `each` that populates 'thisTimeSeries', but before you exit
// the 'success' handler
if (typeof downloadCallback === 'function') {
downloadCallback();
}
And then your invocation would be something like:
var watauga = new Site('03479000');
var downloadCallback = function() {
watauga.timeSeriesList[0].observations[0];
};
watauga.downloadData(downloadCallback);
That way, you're guaranteed that the data is finished processing before you attempt to access it.
If you're getting an undefined in some other part of your code, of course, then there may be something else wrong. Throw a debugger on it and step through the execution. Just bear in mind that interactive debugging has many of the same problems as interactively calling the script; the script has time to complete its download in the background before you start inspecting the variables, which makes it look like everything's hunky dory, when in fact a non-interactive execution would have different timing.
The real issue, I discovered through just starting over from scratch on this function, is something wrong with my implementation of jQuery.().each(). My second stab at the issue, I successfully used a standard for in loop. Here is the working code.
function getCfs() {
for (var index in this.timeSeriesList) {
if (this.timeSeriesList[index].variableCode === '00060'){
return JSON.stringify(this.timeSeriesList[index].observations);
}
}
}
Also, some of the stuff you are talking about #Palpatim, I definitely will have to look into. Thank you for pointing out these considerations. This looks like a good time to further investigate these promises things.

using values from asyncronius function in syncronius function

First, please excuse my bad English. I'm not use to write in English.
I'm using Node.js and i have variables that sometimes get their value from async functions and sometimes by direct assignment (ex:
async(function(data) {
var x= data.something
}; or x = 5;)
the problem is that later on the have shared code which forces me to duplicate the code.
in syncronius scripting i usually do an if.. else statement to seperate the cases and assign. ex:
if(boolivar){
var x = niceFunc();
}
else {
var x = 5;
}
coolFunc(x);
now days im forced to to this:
if(boolivar){
niceFUnc(function(X){
coolFunc(X);
}
}
else{
var x = 5;
coolFunc(X);
}
does someone has an idea how to solve my problem?
I thought about forcing the async function to be sync but:
a. i dont know how
b. it kind of ruins the whole point
I would do it essentially as you have, except that I would abstract the sync/async calls so that it doesn't make any difference to the code that's using it what's really happening behind the scenes (ignore the bad function names; I have no idea what your code does):
function doNiceFunc(boolivar, callback) {
if (boolivar) {
niceFUnc(function(x) {
callback(x);
});
} else {
callback(5);
}
}
Now doNiceFunc appears the same in both cases from the outside:
doNiceFunc(boolivar, function(x) {
coolFunc(x);
});
I've used this exact pattern in a library that retrieved data that was sometimes immediately available and other times had to be retrieved from the network. The users of this library didn't have to care which was the case at any given time, because the call looked the same in both situations.
I tend to use this lib node-sync
var sync = require('sync');
sync(function(){
var result = query.sync(query, params);
// result can be used immediately
})

Categories

Resources