node.js - cannot access variables of outer callback from inner callback - node.js

In my discord bot, I have some code that looks something like this (with portions impertinent to the question omitted):
run(message) {
[...]
mongoClient.connect(config.mongodb.url, function(err, db) {
[...]
var roleMessage = `\`\`\`Roles on ${message.guild.name} (use !role:add <role> to assign them):`
var isNotificationsEnabled = false
db.collection('roles').find({"sid": message.guild.id}).each(function (err, doc) {
[...]
if(doc) {
if(doc.rolename != '$notify') {
roleMessage += `${doc.rolename}\n`
} else {
isNotificationsEnabled = true
}
} else {
[...]
}
})
[...]
})
}
My problem is that variables from the mongoClient.connect() callback are inaccessible from within the inner callback (specifically roleMessage and isNotificationsEnabled) - I'm trying to build a string out of database elements in a MongoDB database. Is there any way I can make these variables accessible, or is there a better way of doing things?
Thanks in advance for anyone who can help.

There is nothing wrong with javascript you can see the example below related to scope access to variables,
let message = { some: 'thing' }
let items = [ 'item1', 'item2' ]
let something = message => {
let newvar = 'scope1';
items.forEach(function(item) {
items.forEach(function(item2) {
console.log(item2 + ' ' + item + ' ' + newvar + ' ' + message.some)
})
})
}
something(message)
db.collection('roles').find({"sid": message.guild.id}).each(function (err, doc) {
May not be returning any data from the collection.

Related

Resolving a recursive socket.on("data") call

I have a node socket server that needs to receive data more than once during a single socket.on('data') call. It's sorta like socket.on('data') is running recursively. I think I've just configured this really really wrong, but I don't know enough NodeJS to figure it out.
Here's an example of the issue:
const net = require("net");
const socket = new net.Socket();
const port = process.argv[2];
socket.connect(port, "127.0.0.1", () => {
// We communicate with another server, always with "type" -> "result" messages.
socket.write(JSON.stringify({ type: "connect", result: "JavaScript" }));
});
function get_query(query) {
queryMessage = JSON.stringify({
type: "query",
result: query,
});
socket.write(queryMessage);
// This is the critical line that I don't know how to resolve.
// Right at this point I need wait for another socket.on('data') call
return socket.giveMeThatQuery()
}
socket.on("data", (data) => {
let jsonData = JSON.parse(data);
if (jsonData["type"] == "call_func") {
let funcToCall = func_reg[jsonData["result"]];
// This will need to call get_query 0-n times, dynamically
result = funcToCall();
let finishMessage = JSON.stringify({
type: "finish_func",
result,
});
socket.write(finishMessage);
} else if (jsonData["type"] == "query_response") {
// This has to connect back to get_query somehow?
// I really have no idea how to approach this.
// async functions? globals?
giveDataToQuery()
}
});
function func1() {
return "bird"; // some funcs are simple
}
function func2() {
// some are more complicated
let data = get_query("Need " + get_query("some data"));
return "cat" + data;
}
function func3() {
let data = get_query("Need a value");
let extra = '';
if (data == "not good") {
extra = get_query("Need more data");
}
return data + "dog" + extra;
}
var func_reg = { func1, func2, func3};
This server is just an example, I can edit or provide more context if it's needed. I suppose if you're looking at this question right after it's posted I can explain more on the live stream.
Edit: adding an example of using globals and while true (this is how I did it in Python). However, in Python, I also put the entire funcToCall execution on another thread, which it doesn't seem like is possible in Node. I'm beginning to think this is actually just impossible, and I need to refactor the entire project design.
let CALLBACK = null;
function get_query(query) {
queryMessage = JSON.stringify({
type: "query",
result: query,
});
socket.write(queryMessage);
while (true) {
if (CALLBACK) {
let temp = CALLBACK;
CALLBACK = null;
return temp;
}
}
...
} else if (jsonData["type"] == "query_response") {
CALLBACK = jsonData["result"];
}
I was correct, what I wanted to do was impossible. Node (generally) only has one thread, ever. You can only be waiting in one place at a time. Async threads or callbacks doesn't solve this, since the callback I would need is another socket.on, which makes no sense.
The solution is yield. This allows me to pause the execution of a function and resume at a later time:
function* func2() {
let data = (yield "Need " + (yield "some data"));
yield "cat" + data;
yield 0;
}
function* func3() {
let data = (yield "Need a value");
let extra = '';
if (data == "not good") {
extra = (yield "Need more data");
}
yield data + "dog" + extra;
yield 0;
}
I updated the server.on to handle these generator function callbacks and respond differently to the yield 0;, which indicates the function has no queries. Here's the final server.on.
server.on("data", (data) => {
try {
let jsonData = JSON.parse(data);
let queryResult= null;
let queryCode = null;
if (jsonData["type"] == "call_func") {
var script = require(jsonData["func_path"]);
FUNC_ITER = script.reserved_name();
} else if (jsonData["type"] == "queryResult") {
queryResult = jsonData["result"];
}
queryCode = FUNC_ITER.next(queryResult);
if (queryCode.value == 0) {
let finishMessage = JSON.stringify({
type: "finish_func",
result: null,
});
server.write(finishMessage);
} else {
let exeMessage = JSON.stringify({
type: "queryCode",
result: queryCode.value,
});
server.write(exeMessage);
}
} catch (err) {
crashMessage = JSON.stringify({
type: "crash",
result: err.stack,
});
server.write(crashMessage);
}
});
And an example of the node files I generated containing the custom functions:
function* reserved_name() {
(yield "return " + String("\"JavaScript concat: " + (yield "value;") + "\"") + ";")
yield 0;
}
module.exports = { reserved_name };
If you're wondering, this is for a language called dit, which can execute code in other languages. There's a Python server that talks to these other languages. Here's an example I finished last week which uses Python, Node, and Lua as the guest languages:

Prompt: Is there a function to check if a prompt is already opened?

Title explains it.
So far, I've tried using variables to help with checking all of this, but it really doesn't help. At all.
I was expecting to find some kind of if(prompt.prompted) or anything like that but there isn't anything.
I'm using this library for prompts: https://www.npmjs.com/package/prompt
var alreadyAsked = 1
function askForMessage() {
prompt.start();
if(alreadyAsked = 0) {
// already asked - true
return;
}
if(alreadyAsked = 1) {
// already asked - false
var alreadyAsked = 0
prompt.get(['Message'], function (err, result) {
if (err) { return onErr(err); }
client.channels.cache.get('789978401929822211').send(result.Message);
var alreadyAsked = 1
})
}
}
client.on("message", async message => {
const args = message.content.slice(0).trim().split(/ +/g);
const messageReceived = args.join(" ")
console.log(`\n${message.author.username}#${message.author.discriminator}: ${messageReceived}`)
askForMessage()
})
function onErr(err) {
console.log(err);
return 1;
}
You had several logical errors.. first being you're starting a new prompt whether or whether not one was already asked.. second being your checks to see if alreadyAsked assigns a number instead of checking for a number.. third being you assign a new variable IN the scope of your function instead of rewriting the variable you're looking at
Check if the below example works
var alreadyAsked=1
function askForMessage() {
if(alreadyAsked == 0) {
// already asked - true
return;
}
if(alreadyAsked == 1) {
// already asked - false
prompt.start();
alreadyAsked = 0
prompt.get(['Message'], function (err, result) {
if (err) { return onErr(err); }
client.channels.cache.get('789978401929822211').send(result.Message);
var alreadyAsked = 1
})
}
}
client.on("message", async message => {
const args = message.content.slice(0).trim().split(/ +/g);
const messageReceived = args.join(" ")
console.log(`\n${message.author.username}#${message.author.discriminator}: ${messageReceived}`)
askForMessage()
})
function onErr(err) {
console.log(err);
return 1;
}

Node JS: Code Refactor with almost similar codes in different forEach

Say, I have some code which looks like follows:-
objects1.forEach(function check(o1) {
if (o1.name === object.name) {
object.name = object.name + objectType;
}
});
objects2.forEach(function ifNameMatch(o2) {
if (o2.name === object.name) {
object.name = object.name + objectType;
}
});
Is there any way I could replace this 2 forEach by one or write it in the more better way?
Any suggestion that you could give would be appreciated.
First I would create a generator for each object I have to do it, if its a repetitive process, then I would return a function that would process collections which is objects1 or objects2 in your scenario.
let nameMatchGenerator = (object, objectType) => {
return (collections) => {
return collections.forEach(collection) => {
collection.forEach(o => {
if (o.name === object.name) {
object.name = object.name + objectType;
}
})
}
}
let nameMatch = (object, objectType);
nameMatch([objects1, objects2]) // you can use this function to repeat
// the process for as many collections

Get placeholder values on firebase onWrite database function

I want to implement a counter function to add up number of likes on a post, here is the code (taken from the firebase counter function example) I have modified it a little, how do I get the placeholder values specificed in the database ref (cid, coid)?
exports.countCommentChange = functions.database.ref('/clipComments/{cid}/{coid}').onWrite(event => {
const db = admin.database();
const clipRef = db.ref(`/clips/${cid}`); // <- how do I get CID?
const countRef = clipRef.child('comments');
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
}
else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
}).then(() => {
console.log('Counter updated.');
});
});
my database structure is as follows:
clips: {
clipId: {
name: "my awesome clip",
likes: 0,
comments: 0
}
},
clipComments: {
clipId:
{
commentTimestamp: {
comment: "awesome video!"
}
}
}
If You console.log your event from the onWrite listener, You will able to see the whole data stored in this object at the dashboard console.
You should notice a object at the beginning named params. This Object will store all placeholder variables in the exported function.
In Your case, You should be able to access your placeholder with event.params.cid.
I hope it helps!

Proper way to make callbacks async by wrapping them using `co`?

It is 2016, Node has had nearly full ES6 support since v4, and Promises have been around since 0.12. It's time to leave callbacks in the dust IMO.
I'm working on a commander.js-based CLI util which leverages a lot of async operations - http requests and user input. I want to wrap the Commander actions in async functions so that they can be treated as promises, and also to support generators (useful for the co-prompt library I'm using for user input).
I've tried wrapping the CB with co in two ways:
1)
program.command('myCmd')
.action(program => co(function* (program) {...})
.catch(err => console.log(err.stack)) );
and
2) program.command('myCmd').action(co.wrap(function* (program) { .. }));
The problem with 1) is that the program parameter isn't passed
The problem with 2) is that errors are swallowed...
I'd really like to get this working as it yields much nicer code in my use case - involving a lot of http requests and also waiting for user input using the co-prompt library..
Is it a better option altogether perhaps to wrap program.Command.prototype.action somehow?
thanks!
I've used a bespoke version of something like co to get a db.exec function which uses yield to do database request. You can pass parameters into a generator function (I pass in a connection object - see the comment where I do it).
Here is by db.exec function that is very similar to what co does
exec(generator) {
var self = this;
var it;
debug('In db.exec iterator');
return new Promise((accept,reject) => {
debug('In db.exec Promise');
var myConnection;
var onResult = lastPromiseResult => {
debug('In db.exec onResult');
var obj = it.next(lastPromiseResult);
if (!obj.done) {
debug('db.exec Iterator NOT done yet');
obj.value.then(onResult,reject);
} else {
if (myConnection) {
myConnection.release();
debug('db.exec released connection');
}
accept(obj.value);
debug('db.exec Promise Resolved with value %d',obj.value);
}
};
self._connection().then(connection => {
debug('db.exec got a connection');
myConnection = connection;
it = generator(connection); //This passes it into the generator
onResult(); //starts the generator
}).catch(error => {
logger('database', 'Exec Function Error: ' + error.message);
reject(error);
});
});
}
the connection object also wraps by database connection object and provides a generator function ability to process the rows of the results from the database, but I won't post that here (although the example below is using it to process the rows).
Here is an example of using the exec function to run a sequence of sql
db.exec(function*(connection) {
if (params.name === ADMIN_USER) {
debug('Admin Logon');
user.name = ADMIN_DISPLAY;
user.keys = 'A';
user.uid = 0;
let sql = 'SELECT passwordsalt FROM Admin WHERE AdminID = 0';
connection.request(sql);
yield connection.execSql(function*() {
let row = yield;
if (row) {
user.nopass = (row[0].value === null);
} else {
user.nopass = false;
}
debug('Admin Password bypass ' + user.nopass.toString());
});
} else {
debug('Normal User Logon');
let sql = `SELECT u.UserID,PasswordSalt,DisplayName,AccessKey,l.LogID FROM Users u
LEFT JOIN UserLog l ON u.userID = l.userID AND DATEDIFF(D,l.LogDate,GETDATE()) = 0
WHERE u.UserName = #username`;
let request = connection.request(sql);
request.addParameter('username',db.TYPES.NVarChar,params.name);
let count = yield connection.execSql(function*() {
let row = yield;
if (row) {
user.uid = row[0].value;
user.name = row[2].value;
user.keys = (row[3].value === null) ? '' : row[3].value;
user.nopass = (row[1].value === null) ;
user.lid = (row[4].value === null) ? 0 : row[4].value;
debug('Found User with uid = %d and lid = %d, keys = %s',
user.uid, user.lid, user.keys);
}
});
if (count === 0) {
debug('Not Found User');
// couldn't find name in database
reply(false,false);
return;
}
}
if (!user.nopass) {
debug('Need a Password');
//user has a password so we must check it
passGood = false; //assume false as we go into this
let request = connection.request('CheckPassword');
request.addParameter('UserID',db.TYPES.Int,user.uid);
request.addParameter('password',db.TYPES.VarChar,params.password);
yield connection.callProcedure(function*() {
let row = yield;
if (row) {
//got a valid row means we have a valid password
passGood = true;
}
});
} else {
passGood = true;
}
if (!passGood) {
debug('Not a Good Pasword');
reply(false,true);
} else {
if (user.uid !== 0 && user.lid === 0) {
let sql = `INSERT INTO UserLog(UserID,LogDate,TimeOn,UserName) OUTPUT INSERTED.logID
VALUES(#uid,GETDATE(),GETDATE(),#username)`;
let request = connection.request(sql);
request.addParameter('uid',db.TYPES.Int,user.uid);
request.addParameter('username',db.TYPES.NVarChar,user.name);
yield connection.execSql(function*() {
let row = yield;
if (row) {
user.lid = row[0].value;
debug('Users Log Entry = %d',user.lid);
}
});
}
reply(true,user);
}
})
.catch((err) => {
logger('database','Error on logon: ' + err.message);
reply(false,false);
});
});
There is a quite simple way to do async function in Commander.js
async function run() {
/* code goes here */
}
program
.command('gettime')
.action(run);
program.parse(process.argv);

Resources