node.js + socket.io + multiple database calls needed for result - javascript

I need help.
I've been trying to wrap my head around async programming with node.js and socket.io for a day now. I understand that I need some flow control but I don't seem to understand how to properly implement it.
I have a redis datastore that has modules stored in a set let's say 'moda','modb'
instances of those modules are 'moda:instances' and in 'modb:instances' the properties of those instances are stored in 'moda:instancea' and 'modb:instanceb' as a hash.
I am trying to get the following json:
"moda": {"instancea": {"property1": "value1", "property2", "value2"}}, "modb": {"instanceb": {"property1": "value1"}}
Could someone give me a little push in the right direction?
Here is my current code:
var io = require('socket.io').listen(2000);
var redis = require('redis').createClient();
var http = require('http');
var async = require('async');
var step = require('step');
io.sockets.on('connection', function (socket) {
var notifications = require('redis').createClient();
notifications.subscribe("notification");
notifications.on("message", function (channel, message) {
socket.send(message);
console.log(channel + ':' + message);
});
socket.on('modules', function(params, callback) {
var response = {};
async.series([
function (callback) {
console.log('1>');
redis.smembers('modules', function (err, modules) {
async.forEachSeries(modules, function(module, moduleCallback) {
response[module] = {}
redis.smembers(module + ':instances', function(err, instances) {
async.forEachSeries(instances, function(instance, instanceCallback) {
response[module][instance] = {}
console.log('2>' + module + ':' +instance);
instanceCallback();
});
moduleCallback();
});
});
callback();
});
},
function (callback) {
console.log('3');
callback();
}
], function() {
console.log(JSON.stringify(response));
});
});
});
The output from this code is:
info - socket.io started
debug - client authorized
info - handshake authorized JMMn1I8aiOMGCMPOhC11
debug - setting request GET /socket.io/1/websocket/JMMn1I8aiOMGCMPOhC11
debug - set heartbeat interval for client JMMn1I8aiOMGCMPOhC11
debug - client authorized for
debug - websocket writing 1::
1>
3
{"moda":{}}
2>moda:instancea
2>moda:instanceb
2>modb:instancea

The problem comes from the fact forEachSeries requires an additional callback as a third parameter (to be called when the whole processing is complete). You cannot just put some code after forEachSeries hoping it will be called once it is complete.
Here is your code modified:
var response = {};
async.series([
function (callback) {
console.log('1>');
redis.smembers('modules', function (err, modules) {
async.forEachSeries(modules, function(module, moduleCallback) {
response[module] = {}
redis.smembers(module + ':instances', function(err, instances) {
async.forEachSeries(instances, function(instance, instanceCallback) {
response[module][instance] = {}
console.log('2>' + module + ':' +instance);
instanceCallback();
}, moduleCallback );
});
}, callback );
});
},
function (callback) {
console.log('3');
callback();
}],
function() {
console.log(JSON.stringify(response));
});
Note how the callback, and moduleCallback are used as a third parameter. The output is:
1>
2>moda:instancea
2>moda:instanceb
2>modb:instancea
3
{"moda":{"instancea":{},"instanceb":{}},"modb":{"instancea":{}}}
which I guess is what you expected.
Additional remark: forEachSeries will process everything in sequence, the next operation waiting for the previous one to complete. This will generate plenty of roundtrips to Redis. forEach should be much more efficient here to leverage pipelining.

Take a look also at promise concept, with promises you configure the flow much more readable way.
First you need to prepare promise versions of redis methods that you're using:
var promisify = require('deferred').promisify;
var RedisClient = require('redis').RedisClient;
RedisClient.prototype.psmembers = promisify(RedisClient.prototype.smembers);
Then you can construct your flow as:
console.log('1>');
redis.psmembers('modules').map(function (module) {
response[module] = {};
return redis.psmembers(module + ':instances').map(function (instance) {
response[module][instance] = {};
console.log('2>' + module + ':' +instance);
});
}).end(function () {
console.log('3');
console.log(JSON.stringify(response));
});
Check: https://github.com/medikoo/deferred

Related

Allow a function to be called only after a set amount of time has passed

To provide context, here's the problem I'm attempting to solve:
I've made a giphy bot for a casual groupchat with friends of mine. By typing /giphy [terms] in a message, it will automatically post the top result for [terms]. My friends, being the rambunctious assholes that they are, quickly started abusing it to spam the groupchat. What I would like to do to prevent this is only allow my postMessage function to be called once per minute.
What I've tried:
Using setTimeout(), which doesn't do exactly what I'd like, since it will only call the function after the amount of time specified in the argument has passed. As far as I can tell, this will cause a delay in messages from the time the bot is called, but it won't actually prevent the bot from accepting new postMessage() calls in that time.
Using setInterval(), which just causes the function to be called forever at a certain interval.
What I think might work:
Right now, I'm working with two .js files.
Index.js
var http, director, cool, bot, router, server, port;
http = require('http');
director = require('director');
bot = require('./bot.js');
router = new director.http.Router({
'/' : {
post: bot.respond,
get: ping
}
});
server = http.createServer(function (req, res) {
req.chunks = [];
req.on('data', function (chunk) {
req.chunks.push(chunk.toString());
});
router.dispatch(req, res, function(err) {
res.writeHead(err.status, {"Content-Type": "text/plain"});
res.end(err.message);
});
});
port = Number(process.env.PORT || 5000);
server.listen(port);
function ping() {
this.res.writeHead(200);
this.res.end("This is my giphy side project!");
}
Bot.js
var HTTPS = require('https');
var botID = process.env.BOT_ID;
var giphy = require('giphy-api')();
function respond() {
var request = JSON.parse(this.req.chunks[0]);
var giphyRegex = /^\/giphy (.*)$/;
var botMessage = giphyRegex.exec(request.text);
var offset = Math.floor(Math.random() * 10);
if(request.text && giphyRegex.test(request.text) && botMessage != null) {
this.res.writeHead(200);
giphy.search({
q: botMessage[1],
rating: 'pg-13'
}, function (err, res) {
try {
postMessage(res.data[offset].images.downsized.url);
} catch (err) {
postMessage("There is no gif of that.");
}
});
this.res.end();
} else {
this.res.writeHead(200);
this.res.end();
}
function postMessage(phrase) {
var botResponse, options, body, botReq;
botResponse = phrase;
options = {
hostname: 'api.groupme.com',
path: '/v3/bots/post',
method: 'POST'
};
body = {
"bot_id" : botID,
"text" : botResponse
};
botReq = HTTPS.request(options, function(res) {
if(res.statusCode == 202) {
} else {
console.log('Rejecting bad status code: ' + res.statusCode);
}
});
botReq.on('error', function(err) {
console.log('Error posting message: ' + JSON.stringify(err));
});
botReq.on('timeout', function(err) {
console.log('Timeout posting message: ' + JSON.stringify(err));
});
botReq.end(JSON.stringify(body));
}
exports.respond = respond;
Basically, I'm wondering where would be the ideal place to implement the timer that I'm envisioning. It seems like I would want to have it only listen for /giphy [terms] after one minute, rather than waiting one minute to post.
My Question(s):
Would the best way to go about this be to set a timer on the response() function, since then it will only actually parse the incoming information once per minute? Is there a more elegant place to put this?
How should the timer work on that function? I don't think I can just run response() once every minute, since that seems to mean it'll only parse incoming json from the GroupMe API once per minute, so it could potentially miss incoming messages that I would want it to capture.
Store the time when a request is made and then use that to see if subsequent requests should be ignored if these are executed to fast.
var waitTime = 10*1000; // 10 s in millis
var lastRequestTime = null;
function respond() {
if(lastRequestTime){
var now = new Date();
if(now.getTime() - lastRequestTime.getTime() <= waitTime){
this.res.writeHead(200);
this.res.end("You have to wait "+waitTime/1000+" seconds.");
return;
}
}
lastRequestTime = new Date();
postMessage();
}

This code doesn't seem to fire in order?

My problem is that the code does not seem to be running in order, as seen below.
This code is for my discord.js bot that I am creating.
var Discord = require("discord.js");
var bot = new Discord.Client();
var yt = require("C:/Users/username/Documents/Coding/Discord/youtubetest.js");
var youtubetest = new yt();
var fs = require('fs');
var youtubedl = require('youtube-dl');
var prefix = "!";
var vidid;
var commands = {
play: {
name: "!play ",
fnc: "Gets a Youtube video matching given tags.",
process: function(msg, query) {
youtubetest.respond(query, msg);
var vidid = youtubetest.vidid;
console.log(typeof(vidid) + " + " + vidid);
console.log("3");
}
}
};
bot.on('ready', () => {
console.log('I am ready!');
});
bot.on("message", msg => {
if(!msg.content.startsWith(prefix) || msg.author.bot || (msg.author.id === bot.user.id)) return;
var cmdraw = msg.content.split(" ")[0].substring(1).toLowerCase();
var query = msg.content.split("!")[1];
var cmd = commands[cmdraw];
if (cmd) {
var res = cmd.process(msg, query, bot);
if (res) {
msg.channel.sendMessage(res);
}
} else {
let msgs = [];
msgs.push(msg.content + " is not a valid command.");
msgs.push(" ");
msgs.push("Available commands:");
msgs.push(" ");
msg.channel.sendMessage(msgs);
msg.channel.sendMessage(commands.help.process(msg));
}
});
bot.on('error', e => { console.error(e); });
bot.login("mytoken");
The youtubetest.js file:
var youtube_node = require('youtube-node');
var ConfigFile = require("C:/Users/username/Documents/Coding/Discord/json_config.json");
var mybot = require("C:/Users/username/Documents/Coding/Discord/mybot.js");
function myyt () {
this.youtube = new youtube_node();
this.youtube.setKey(ConfigFile.youtube_api_key);
this.vidid = "";
}
myyt.prototype.respond = function(query, msg) {
this.youtube.search(query, 1, function(error, result) {
if (error) {
msg.channel.sendMessage("There was an error finding requested video.");
} else {
vidid = 'http://www.youtube.com/watch?v=' + result.items[0].id.videoId;
myyt.vidid = vidid;
console.log("1");
}
});
console.log("2");
};
module.exports = myyt;
As the code shows, i have an object for the commands that the bot will be able to process, and I have a function to run said commands when a message is received.
Throughout the code you can see that I have put three console.logs with 1, 2 and 3 showing in which order I expect the parts of the code to run. When the code is run and a query is found the output is this:
I am ready!
string +
2
3
1
This shows that the code is running in the wrong order that I expect it to.
All help is very highly appreciated :)
*Update! Thank you all very much to understand why it isn't working. I found a solution where in the main file at vidid = youtubetest.respond(query, msg) when it does that the variable is not assigned until the function is done so it goes onto the rest of my code without the variable. To fix I simply put an if statement checking if the variable if undefined and waiting until it is defined.*
Like is mentioned before, a lot of stuff in javascript runs in async, hence the callback handlers. The reason it runs in async, is to avoid the rest of your code being "blocked" by remote calls. To avoid ending up in callback hell, most of us Javascript developers are moving more and more over to Promises. So your code could then look more like this:
myyt.prototype.respond = function(query, msg) {
return new Promise(function(resolve, reject) {
this.youtube.search(query, 1, function(error, result) {
if (error) {
reject("There was an error finding requested video."); // passed down to the ".catch" statement below
} else {
vidid = 'http://www.youtube.com/watch?v=' + result.items[0].id.videoId;
myyt.vidid = vidid;
console.log("1");
resolve(2); // Resolve marks the promises as successfully completed, and passes along to the ".then" method
}
});
}).then(function(two) {
// video is now the same as myyt.vidid as above.
console.log(two);
}).catch(function(err) {
// err contains the error object from above
msg.channel.sendMessage(err);
})
};
This would naturally require a change in anything that uses this process, but creating your own prototypes seems.. odd.
This promise returns the vidid, so you'd then set vidid = youtubetest.response(query, msg);, and whenever that function gets called, you do:
vidid.then(function(id) {
// id is now the vidid.
});
Javascript runs async by design, and trying to hack your way around that leads you to dark places fast. As far as I can tell, you're also targetting nodeJS, which means that once you start running something synchronously, you'll kill off performance for other users, as everyone has to wait for that sync call to finish.
Some suggested reading:
http://callbackhell.com/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://stackoverflow.com/a/11233849/3646975
I'd also suggest looking up ES6 syntax, as it shortens your code and makes life a hellofalot easier (native promises were only introduced in ES6, which NodeJS 4 and above supports (more or less))
In javascript, please remember that any callback function you pass to some other function is called asynchronously. I.e. the calls to callback function may not happen "in order". "In order" in this case means the order they appear on the source file.
The callback function is simply called on certain event:
When there is data to be processed
on error
in your case for example when the youtube search results are ready,
'ready' event is received or 'message' is received.
etc.

Is there a good example of lodash's _.after method

Is there a good practical example of how to use _.after method in lodash library?
Use it whenever you need to invoke a callback after it's been called n number of times.
var fn = _.after(3, function () {
console.log('done');
});
fn(); // Nothing
fn(); // Nothing
fn(); // Prints "done"
It's useful for invoking callback when all async calls are complete.
var done = _.after(3, function () {
console.log('all 3 requests done!');
});
$.get('https://example.com', done);
$.get('https://example.com', done);
$.get('https://example.com', done);
Basic game example where player dies after getting shot 3 times.
var isDead = _.after(3, function () {
console.log('Player died!');
});
player1.shoot(player2, isDead); // undefined
player1.shoot(player2, isDead); // undefined
player1.shoot(player2, isDead); // "Player died!"
Basically you use _.after in place of a manual counter.
There are not many examples out there but see the following for something that I use for my internal tooling.
Basically the following is a script to be run using Node. It removes documents from given Mongodb collections. That is it. But the idea is to close the DB connection only after all collections are cleaned up. We will use _.after method for that. You can read about after function here
var Db = require('mongodb').Db,
MongoClient = require('mongodb').MongoClient,
Server = require('mongodb').Server;
_ = require('lodash');
var db = new Db('mydb', new Server('localhost', 27017));
db.open(function(err, db) {
var collectionsToClean = ['COLLECTIONA', 'COLLECTIONB', 'COLLECTIONC'];
var closeDB = _.after(collectionsToClean.length, function() {
db.close();
console.log('Connection closed');
});
_.forEach(collectionsToClean, function(collectionName) {
db.collection(collectionName, function(err, collection) {
collection.remove({}, function(err) {
if (err) {
console.log('Could not remove documents from ' + collectionName);
} else {
console.log("All documents removed from " + collectionName);
}
closeDB();
});
})
});
});
You can now use this as a template for other Mongodb shell methods.

Fiber Error with npm package serial-port with meteor

I'm using the SerialPort npm package with meteor. I've used wrapAsync to list Serial ports but i don't know how to do with the serialPort.on method.
I've an error when i want to inser datas in my Cars collection :
Meteor code must always run within a Fiber. Try wrapping callbacks
that you pass to non-Meteor libraries with Meteor.bindEnvironment.
Code :
Meteor.startup(function () {
SerialPort = Meteor.npmRequire('serialport');
// Wrap method SerialPort.list to call it Synchronously
listSerialPorts = function(callback) {
SerialPort.list(function (err, ports) {
callback(null, ports);
});
}
// Reset cars collection
});
Meteor.methods({
serialPortsRefresh: function () {
// TODO : problem when several arduinos ?
Config.remove({key:'serialPorts'});
// Call SerialPort.list
var asyncListSerialPorts = Meteor.wrapAsync(listSerialPorts);
var resultsListSerialPorts = asyncListSerialPorts();
// Insert results in database
var configSerialPorts = {key: "serialPorts", value: resultsListSerialPorts[0].comName };
Config.insert(configSerialPorts);
},
// Connect Serial port
serialPortConnect: function (port) {
// debugger;
// serialPort = new SerialPort(port.value, {baudrate: 9600});
serialPort = new SerialPort.SerialPort("/dev/ttyUSB0", {baudrate: 9600, parser: SerialPort.parsers.readline("\n")});
// connectSerialPort(port);
serialPort.on('open', function() {
console.log('Port ' + port.value + ' open');
});
serialPort.on('data', function(data) {
dispatchMessages(data);
//Watchdog.insert({key: "Receiving data", value: data })
});
sendToArduino = function(message) {
console.log(message);
serialPort.write(message);
};
dispatchMessages = function(data) {
console.log(data);
//Split data
var datas = data.split(" ");
if (datas[1] == "OK") {
console.log("Car " + datas[0] + " is here");
// Add car to database
Cars.insert({
cid: datas[0],
active: true
});
}
};
},
// Ping bridge
ping: function () {
sendToArduino("LED13\n");
}
});
The problem is that the callbacks you're passing to serialPort.on will not run within the same fiber as your method when they're invoked. In fact, they won't run within a fiber at all, unless you wrap them appropriately.
Meteor.bindEnvironment runs the passed function within a fiber, but also copies in the surrounding environment, which is necessary as Meteor stores all sorts of variables within the current fiber which might be required to run the callback in question.
So, if you do this it should work:
serialPort.on('open', Meteor.bindEnvironment(function() {
// Wrapping this one is unnecessary at present as it doesn't
// do anything that needs to be run in a fiber, but you should
// probably wrap it anyway so that you can safely add more code
// if required.
console.log('Port ' + port.value + ' open');
}, function(e) {
// This is an error-handler - you don't have to pass one, but
// if you don't it can make debugging a nightmare.
throw e;
}));
serialPort.on('data', Meteor.bindEnvironment(function(data) {
dispatchMessages(data);
//Watchdog.insert({key: "Receiving data", value: data })
}, function(e) {
throw e;
}));
Note that you also need to wrap callbacks within callbacks, etc., which can become quite verbose (and makes putting something like var mBE = Meteor.bindEnvironment at the top of your methods file quite a good idea).

Async in getting files in folder?

I am new in Node.js . All I did in here is get file list in folder... Everything in Node.js seem to async but my function I want it in synchronous way.
So, I do as follow.
function getFiles() {
var file = [];
var walker = walk.walk('./files');
walker.on('file', function (root, stat, next) {
file.push(root + '/' + stat.name);
next();
})
walker.on('end', function () {
console.log(JSON.stringify(file));
})
return file;}
It worked as I expected :
["./files/1.mp3","./files/2.mp3","./files/3.mp3","./files/4.mp3","./files/5.mp3","./files/6.mp3","./files/7.mp3","./files/8.mp3"]
but when I assigned that function to variable
var list = getFiles();
response.writeHead(200, {'Content-type':'application/json'});
response.end(JSON.stringify(list));
It always returned nothing, I think that getFiles() run in another thread so can not receive value of data. Thank for your reading.
I can shed some light on the behavior you are experiencing by outlining the flow of the application as it is run:
call to getFiles
declare files array and walker
bind walker event "file" and "end" to callbacks
return files array
walker file event fires
walker end event fires
as you can see the events are firing out of band with the method call. To deal with this the common node.js approach is to setup your code something like the following:
function getFiles(callback) {
var file = [];
var walker = walk.walk('./files');
walker.on('file', function (root, stat, next) {
file.push(root + '/' + stat.name);
next();
})
walker.on('end', function () {
callback(file);
})
}
now when you go to execute this method you would do something like this:
getFiles(function(list){
response.writeHead(200, {'Content-type':'application/json'});
response.end(JSON.stringify(list));
});
obviously this is a little unsightly since the controller now has to create a callback scenario and the getFiles method need to execute that callback in course. Another approach is to use the Promises concept, which I will leave to the discovery of the reader with the following link: https://github.com/kriskowal/q
Async functions return before they are ready
You can't return data thats not there in your own code
Async functions often take a callback argument that is executed when they are ready
You own code could ask for its own callbacks
`
function getFiles(callBack) {
var file = [];
var walker = walk.walk('./files');
walker.on('file', function (root, stat, next) {
file.push(root + '/' + stat.name);
next();
})
walker.on('end', function () {
console.log(JSON.stringify(file));
callBack(file);
})
}
// assuming server environment, express, connect, etc...
app.get('/list', function(req, res){
getFiles(function(D){ res.json(D) });
});
`

Categories

Resources