I'm beginner to NodeJS, so I'm not entirely sure what the best method to achieve this would be. Basically I want to create a global variable with a string, for instance 'USD', that would get updated whenever my 'set currency' event is fired. I want it to remain that way until the event is called again.
I am using EventEmitter to fire off some events, in one of my files I have the following.
var event = require('./events');
if (msg.content.includes('!currency set currency')) {
split = msg.content.split(' ');
event.emit('setCurrency', split[3])
}
And then inside the events file I'm doing something like the following.
var exchangePref;
var event = new events.EventEmitter();
event.on('setExchange', (exchange) => {
exchangePref = exchange;
return exchangePref;
});
modules.exports = event;
I understand that re-writing the variable inside a callback isn't going to do what I need it to do, but I'm quite lost with how to achieve what I need it to do due to the modules.exports = event part at the bottom, the calling function simply never gets the data. I've played around with creating a constructor, but even still I couldn't get it to work.
Any suggestions/ideas would be greatly appreciated.
I wouldn't use event emitter for this. Instead create a module along the lines of:
var exchangePrefs = { currency: "JPY" };
module.exports = {
setCurrency : function(newVal){ exchangePrefs.currency = newVal; },
getCurrency : function(){ return exchangePrefs.currency; }
};
Then in your various other modules you just:
require('./mymodule').setCurrency('USD');
and somewhere else
var currency = require('./mymodule').getCurrency();
I'm sure it can be made prettier, but I think you get the point. For almost all intents and purposes modules work like singletons. There are some gotchas, but nothing you'll run into too often. (Singleton pattern in nodejs - is it needed?)
Personally I'd use some sort of data persistence in the exchangePref-module just for peace of mind. Like redis, or saving to a json-file.
Related
I'm trying to test a really simple increment function in vanilla javascript.
This function have a button with a click event, which triggers the input to sum one to its value.
I've tried to search for help to think how to resolve this problem. I think maybe I should create a mock for the button (instead of access the DOM element), and simulates the click event with Enzyme (but I don't know if it is really necessary).
All I could get in my searches was Jest testing using components from React or Angular, which complicated much more my question and therefore I get no answer for simple JS. The Jest documentation didn't help either.
The code of my function is:
const increment = () => {
$increment.addEventListener("click", function() {
if (+$quantity.value < 100) {
$quantity.value = +$quantity.value + 1;
}
});
};
The full code is on this codesandbox.
Hokay so, my JavaScript is a little rusty but I think I know the problem looking at the code (thank you by the way, it made this way easier to figure out)...
Your instinct that you need a mock is correct, but right now the way your increment function works it's coupled to $increment which is in the local scope (making it really unfun to mock). Instead of using a private variable in the local scope to bind the event listener to, you want to pass the $element into the increment function, and then to add the event listener to it.
const increment = ($element) => {
$element.addEventListener("click", function() {
if (+$quantity.value < 100) {
$quantity.value = +$quantity.value + 1;
}
});
};
In your test now you can create a mock with a function on it called addEventListener... the below is probably not quite right, but I think should get you most of the way there:
// In your test setup, or in the test itself
const myMockElement = {
addEventListener: jest.fn(),
};
// Later in your test
increment(myMockElement);
expect(myMockElement.addEventListener.mock.calls.length).toBe(1);
Just as a note from the code in the event listener, I'd recommend passing it $quantity into the function as well instead of capturing it from the local context/scope/whatever-the-hell-its-exactly-called-in-javascript (i.e. what we did with $element)... it'll make testing things MUCH, MUCH easier to test and make your functions more robust.
Hope this helps!
Earlier I ran into the issue of Alexa not changing the state back to the blank state, and found out that there is a bug in doing that. To avoid this issue altogether, I decided that I wanted to force my skill to always begin with START_MODE.
I used this as my reference, where they set the state of the skill by doing alexa.state = constants.states.START before alexa.execute() at Line 55. However, when I do the same in my code, it does not work.
Below is what my skill currently looks like:
exports.newSessionHandler = {
LaunchRequest () {
this.hander.state = states.START;
// Do something
}
};
exports.stateHandler = Alexa.CreateStateHandler(states.START, {
LaunchRequest () {
this.emit("LaunchRequest");
},
IntentA () {
// Do something
},
Unhandled () {
// Do something
}
});
I'm using Bespoken-tools to test this skill with Mocha, and when I directly feed IntentA like so:
alexa.intended("IntentA", {}, function (err, p) { /*...*/ })
The test complains, Error: No 'Unhandled' function defined for event: Unhandled. From what I gather, this can only mean that the skill, at launch, is in the blank state (because I have not defined any Unhandled for that state), which must mean that alexa.state isn't really a thing. But then that makes me wonder how they made it work in the example code above.
I guess a workaround to this would be to create an alias for every intent that I expect to have in the START_MODE, by doing:
IntentA () {
this.handler.state = states.START;
this.emitWithState("IntentA");
}
But I want to know if there is a way to force my skill to start in a specific state because that looks like a much, much better solution in my eyes.
The problem is that when you get a LaunchRequest, there is no state, as you discovered. If you look at the official Alexa examples, you will see that they solve this by doing what you said, making an 'alias' intent for all of their intents and just using them to change the state and then call themselves using 'emitWithState'.
This is likely the best way to handle it, as it gives you the most control over what state and intent is called.
Another option, assuming you want EVERY new session to start with the same state, is to leverage the 'NewSession' event. this event is triggered before a launch request, and all new sessions are funneled through it. your code will look somewhat like this:
NewSession () {
if(this.event.request.type === Events.LAUNCH_REQUEST) {
this.emit('LaunchRequest');
} else if (this.event.request.type === "IntentRequest") {
this.handler.state = states.START;
this.emitWithState(this.event.request.intent.name);
}
};
A full example of this can be seen here (check out the Handlers.js file): https://github.com/alexa/skill-sample-node-device-address-api/tree/master/src
I would also recommend reading through this section on the Alexa GitHub: https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs#making-skill-state-management-simpler
EDIT:
I took a second look at the reference you provided, and it looks like they are setting the state outside of an alexa handler. So, assuming you wanted to mimic what they are doing, you would not set the state in your Intent handler, but rather the Lambda handler itself (where you create the alexa object).
exports.handler = function (event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = appId;
alexa.registerHandlers(
handlers,
stateHandlers,
);
alexa.state = START_MODE;
alexa.execute();
};
I wanted to use rxjs for the first time but am a bit stucked 'cause it doesn't behave exactly like I want it to: In my scenario I want to create an observable from a promise. But I want the promise only being called once (not on every subscription) and I want it not being called on creation time (defer the call to the first subscription).
First I tried this:
var source = Rx.Observable.fromPromise(_this.getMyPromise())
which causes a call to the getMyPromise function right on creation time. This is not satisfying because at that time I don't know if the source really will be used.
Then I tried:
var source = Rx.Observable.defer(function() { return _this.getMyPromise() })
which causes a call to the getMyPromise function each time a new subscription is being made to source. This makes way too many unnecessary calls to the web server. The Rx.Observable.create function seems to have the same issue.
So what is left or what am I missing?
.shareReplay() does this, e.g.:
var source = Rx.Observable.defer(function() { return _this.getMyPromise() }).shareReplay();
If you're using rxjs5, you'll want to read: Pattern for shareReplay(1) in RxJS5
In answer to your comment below, I can think of a fairly straightforward extension to the above logic that will do what you want, but it has a caveat. Let's say the events you want to use to trigger a "refresh" are represented in a stream, s$, then you could do something like:
var source = Rx.Observable.of({}).concat(s$)
.flatMapLatest(function() {
return Rx.Observable.defer(function() {
return _this.getMyPromise()
})
})
.shareReplay(1)
What we have here is a stream starting with a dummy object to get things rolling, followed by a stream consisting of your refresh events. Each of these is projected into a new observable created from a fresh invocation of your getMyPromise method, and the whole thing is flattened into a single stream. Finally, we keep the shareReplay logic so we only actually make calls when we should.
The caveat is that this will only work properly if there's always at least one subscriber to the source (the first subscription after all others are disposed will run the promise again, and will receive both the previously-cached value and the result of the promise it caused to run).
Here is an answer that does not require at least one subscriber at the source at all times using a simple helper:
var _p = null;
var once = function() { return _p || (_p = _this.getMyPromise());
var source = Rx.Observable.defer(once);
Or if you're using lodash, you can _.memoize your getMyPromise and get this automatically.
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.
I'm reading the Google Drive Realtime API documentation on Building a Collaborative Data Model.
I really like the way gapi.drive.realtime.databinding.bindString behaves. It doesn't mess up your cursor placement when multiple people are typing in the same text box. But it requires that you pass it a CollaborativeString.
But if you register a custom type, you have to use gapi.drive.realtime.custom.collaborativeField no matter what type of field you are defining, and you can't pass one of these to bindString. In fact, the collaborativeField type does not appear to be documented anywhere, and inspecting it in the console shows that it has no methods. That means there's no registerReference method, which CollaborativeString uses to keep track of cursor positions.
How frustrating. So I guess I have to work around it. I see a few options:
Ignore the fact that the cursor gets messed up during collaboration
Use a CollaborativeMap instead of a custom type, and wrap it with my custom type at runtime
Probably going to do option 2.
I think you misunderstand how this site works, the onus is not on other people to show you how to do something - you're asking other people to take time from their day and help you.
That being said, taking a quick look at the page that you linked shows that what you want to do is not only possible but quite straightforward and compatible with bindString. Stealing from the example code from that page:
// Call this function before calling gapi.drive.realtime.load
function registerCustomTypes()
{
var Book = function () { };
function initializeBook()
{
var model = gapi.drive.realtime.custom.getModel(this);
this.reviews = model.createList();
this.content = model.createString();
}
gapi.drive.realtime.custom.registerType(Book, 'Book');
Book.prototype.title = gapi.drive.realtime.custom.collaborativeField('title');
Book.prototype.author = gapi.drive.realtime.custom.collaborativeField('author');
Book.prototype.isbn = gapi.drive.realtime.custom.collaborativeField('isbn');
Book.prototype.isCheckedOut = gapi.drive.realtime.custom.collaborativeField('isCheckedOut');
Book.prototype.reviews = gapi.drive.realtime.custom.collaborativeField('reviews');
Book.prototype.content = gapi.drive.realtime.custom.collaborativeField('content');
gapi.drive.realtime.custom.setInitializer(Book, initializeBook);
}
and
// Pass this as the 2nd param to your gapi.drive.realtime.load call
function onDocLoaded(doc)
{
var docModel = doc.getModel();
var docRoot = docModel.getRoot();
setTimeout(function ()
{
var book = docModel.create('Book');
book.title = 'Moby Dick';
book.author = 'Melville, Herman';
book.isbn = '978-1470178192';
book.isCheckedOut = false;
book.content.setText("Call me Ishmael. Some years ago - never mind how long precisely - having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.");
docRoot.set('tbook', book);
debugger;
}, 0);
}
Good luck and have fun with the Realtime API - it's a lot of fun to play with.
I know this question and answer are getting old, but for reference's sake, just the last part of Grant Watters' very good answer, the onDocLoaded routine, is rather misleading. That function as written, is more suited for the 3rd parameter to the gapi.drive.realtime.load call, the onInitializeModel callback.
The 2nd parameter is called every time the Doc is loaded. You wouldn't normally add the same object over and over as the above routine would... Instead, you would normally set up your event handling, your dataBinds etc. This version might clarify somewhat:
// Pass this as the 2nd param to your gapi.drive.realtime.load call
function onDocLoaded(doc)
{
var docModel = doc.getModel();
var docRoot = docModel.getRoot();
var text = doc.getModel().getRoot().get("text");
// Add an event listener...
text.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, onStringChanged);
// ...and/or bind to collaborative objects:
var textArea = document.getElementById('textArea1')
textBinding = gapi.drive.realtime.databinding.bindString(text, textArea);
etc...
}
Not incidentally, bindString returns the binding object, which is needed to "unbind" later, preventing an AlreadyBound error or other unexpected behavior when the next Doc is loaded. Do something like this:
function onDocLoaded(doc)
{
// Clear any previous bindings etc:
if (textBinding) { textBinding.unbind() };
textBinding = null;
etc...