I'm confused about Node.js its way of handling the variable scope.
I am using a nmp package called googl for my file. The problem lies in the following code:
var q = text.split(/ (.+)/)[1];
var googl = require('goo.gl');
googl.setKey('removed for obvious reasons');
googl.getKey();
googl.shorten('q')
.then(function (shortUrl) {
console.log(shortUrl);
})
.catch(function (err) {
console.error(err.message);
});
return "I minimized " + q + " for you the new URL is: " + shortUrl;
console.log(shortUrl);
}
I want to retrieve the shortUrl and return it (returning it inside the .then won't work) but I am not able to do so. Does anyone know if this is possible?
The problem is not the scope of variables here, but that you incorrectly handle asynchronous code. JS will execute lines 1-4 and then line 5, which returns a promise (as the documentation states "Most methods return promises.").
Then it will immediately execute your return statement with an undefined shortUrl.
Then when your promise is resolved you will log the shortUrl to the console. But this will be after your return.
The best thing would be if you returned the promise and used it in the caller like:
function foo (text) {
var q = text.split(/ (.+)/)[1];
var googl = require('goo.gl');
googl.setKey('removed for obvious reasons');
googl.getKey();
return googl.shorten('q');
}
foo('text').then(function (shortUrl) {
console.log("I minimized " + q + " for you the new URL is: " + shortUrl);
});
Related
I am using expressJs to route some POST requests.
From the client side I pass an object of objects and in the server I iterate over each of them with a for loop.
My problem, the variable cantidad in the loop only takes the first value instead of being refreshed into the pool.query, but before the pool.query it takes the right value.
So, the line below is ok.
console.log("cantidad before query: " + cantidad);
But the line below is bad. It has the first value.
console.log("cantidad in query: " + cantidad);
This is part of my code.
for (var key in objects) {
if (objects.hasOwnProperty(key)) {
...
console.log("cantidad before query: " + cantidad);
pool.query(qProducto,idProducto, function (error, results, fields {
if (error) {
...
} else {
console.log("cantidad in query: " + cantidad);
...
This is the full POST in ExpressJs.
app.post("/commanda", function (req, res) {
var idCuenta = req.body.idCuenta;
var idEmpleado = req.body.idEmpleado;
var fechaRegistro = req.body.fechaRegistro;
var cuenta_mesero = "C:" + idCuenta + ":E:" + idEmpleado;
var objects = req.body.objects;
var element = {};
for (var key in objects) {
if (objects.hasOwnProperty(key)) {
var qProducto = "SELECT descripcionProducto FROM PRODUCTO WHERE idProducto = ? ;";
var descProducto = '';
console.log("cantidad in commanda2 : " + objects[key].cantidad );
try {
pool.query(qProducto, objects[key].idProducto, function (error, results, fields) {
if (error) {
console.error(error);
console.error("Failed with query: " + qProducto);
res.status(500).end();
throw error;
} else {
console.log("cantidad in commanda4 : " + objects[key].cantidad );
descProducto = JSON.stringify(results[0].descripcionProducto);
element = {
idProducto:objects[key].idProducto,
cantidad:objects[key].cantidad,
descProducto:descProducto,
cuenta_mesero:cuenta_mesero,
fechaRegistro:fechaRegistro
};
imprimirOrden(element);
}
});
} catch (error) {
callback(error);
}
}
}
printer.printVerticalTab();
res.status(200).end();
});
This is how an object looks like.
{ '0':
{ idProducto: '28',
cantidad: '3',
descProducto: 'Product1',
precioProducto: '3500',
precioTotal: 10500,
'$$hashKey': 'object:345' },
'1':
{ idProducto: '29',
cantidad: '2',
descProducto: 'Product2',
precioProducto: '4500',
precioTotal: 9000,
'$$hashKey': 'object:346' } }
This happens because the function for is synchronous but the function poll.query is asynchronous. What this means is that using the for loop you are essentially queuing some queries. You are not executing them one by one. So the for loop will finish before even one result is returned from the query. If you want to use data from the query for the next iteration you should start using async.js, an npm module that helps you avoid this problems. TL;DR the console log that you think runs in query is actually run before even one query has finished. More information is needed on where you declare the variable cantidad and when you change it to accurately understand the problem.
UPDATE:
What I told you at first was quite wrong because of the fact that I misunderstood the in-detention of the else {}. But what I told you already is actually the problem. It was well obfuscated.The for loop finishes before even one query has finished. They are just queued. So the second console.log will have the key of the last key in the loop. If you need logic that requires knowing in which iteration you are you should implement an async function in order to know in which iteration you actually are. If you don't want to use the async library you can use something like this.
First add this function in the bottom of your js file
https://pastebin.com/4tR0xaTY
You essentially created an async for loop that you can now know in which iteration you are using loop.iteration(). Then replace your post code with the code written below ( To include the async loop ).
https://pastebin.com/YzZU7bqp
Am new to promisification and am not quite sure if .then and .each carry variables across the entire promise.
Also, I clearly define docReplies in the fourth line, yet the console logs:
Possibly unhandled ReferenceError: docReplies is not defined
Am looking to loop over each element (replyID) in the repliesIDsArray and findOneAsync the message..then for each element in the doc.replies array find the index of the replyID, setting that to index1..then for each element in the doc.replies[index1] array find the index of the username (res.locals.username), setting that to index2..then with index1 and index2, save fields to save to the doc..
(Here's a link to where this derives, with an outline of the db schema if that helps)
Promise.each(repliesIDsArray, function(replyID){
Models.Message.findOneAsync({'_id': req.params.message_id})
.then(function(doc){
var docReplies = [];
pushReplies = docReplies.push(doc.replies);
}).each(docReplies, function (replyIndex){
// loop over doc.replies to find..
// ..the index(index1) of replyID at replies[index]._id
var index1;
if (docReplies[replyIndex]._id == replyID) {
index1 = replyIndex;
}
var docRepliesIndex1 = [];
pushRepliesIndex1 = docRepliesIndex1.push(doc.replies[index1]);
}).each(docRepliesIndex1, function (usernameIndex){
// loop over doc.replies[index1].to and find..
// ..the index(index2) of res.locals.username at replies[index1].to[index2]
var index2;
if (docRepliesIndex1.to[usernameIndex].username === res.locals.username) {
index2 = usernameIndex;
}
}).then(function(index1, index2) {
console.log('index1 = ' + index1);
console.log('index2 = ' + index2);
doc.replies[index1].to[index2].read.marked = true;
doc.replies[index1].to[index2].read.datetime = req.body.datetimeRead;
doc.replies[index1].to[index2].updated= req.body.datetimeRead;
doc.markModified('replies');
var saveFunc = Promise.promisify(doc.save, doc);
return saveFunc();
console.log('doc saved');
}).then(function(saved) {
console.log("Success! doc saved!");
console.log("Sending saved doc:");
res.json(saved);
}).catch(Promise.OperationalError, function(e){
// handle error in Mongoose findOne + save
console.error("unable to save because: ", e.message);
console.log(e);
res.send(e);
throw err;
}).catch(function(err){
// would be a 500 error, an OperationalError is probably a 4XX
console.log(err);
res.send(err);
throw err; // this optionally marks the chain as yet to be handled
});
})
Promises have no magic capability with your variable declarations. docReplies is defined in your first .then() callback function and is only available within that function. If you want it available across many .then() handler functions, then you will need declare it at a higher scope so it's available everywhere (normal Javascript scoping rules).
Or, in certain cases, you can return data from one promise handler to another, but it doesn't sound like that's what you're trying to do here.
In any case, normal Javascript scoping rules apply to all variable declarations, even those in promise callback functions.
I am toying with Q and promptly and I am trying to ask, in a sequence, the user to input some stuff. For example :
What is your name? Bob
What is your age? 40
Hello Bob (40)!
(yes! it's a simple "Hello world!" program.)
And here is the code I am trying, directly from Q's github project page :
Q.fcall(promptly.prompt, "What is your name? ")
.then(promptly.prompt, "What is your age? ")
.done(function(name, age) {
console.log("Hello " + name + " (" + age + ")");
});
});
But it is not working as expected (maybe I'm reading wrong?). Whatever I try, it seems that promptly.prompt is listening to keystroke in parallel, and the .done function is called right away, resulting into a
/path/to/node_modules/promptly/index.js:80
fn(null, data);
^
TypeError: undefined is not a function
at /path/to/node_modules/promptly/index.js:80:9
...
once I hit Enter. Any idea why this is doing so and how I can accomplish what I'm trying to do?
** Edit **
Basically, what my end goal would be to create a reusable function invoked like so :
promptAll({
'name': "What is your name? ",
'age': "What is your age? "
}).done(function(input) {
console.log(input); // ex: { name: "Bob", age: 40 }
});
** Update **
Here's my working solution, I had to use nfcall as suggested by WiredPraine :
function multiPrompt(args) {
function _next() {
if (keys.length) {
var key = keys.pop();
Q.nfcall(promptly.prompt, args[key]).done(function(value) {
result[key] = value;
_next();
});
} else {
def.resolve(result);
}
};
var def = Q.defer();
var keys = _.keys(args).reverse();
var result = {};
_next();
return def.promise;
};
(Note : I am using Underscore, but the same can be achieved with a standard object iterator.)
Below are two approaches.
First, you'd need to use nfcall so that Q uses the NodeJS conventions for callbacks.
But, as the functions aren't promises, you'll need to handle the chaining and synchronous behavior slightly differently.
In the first example, start1, the code creates an instance of defer and returns it as the promise. When the prompt function returns, it resolves the deferred object instance and passes the value of the function (ideally the prompt). It should also handle errors, etc. in "real" code.
In both examples, I've added a function to grab the result of the the promise resolving. It's not passed as parameters to the last done instance. The function passed to done will execute as soon as the first promise is resolved (after the prompt has returned in this case).
var promptly = require('promptly');
var Q = require('q');
// make a simple deferred/promise out of the prompt function
var prompter = function(text) {
var deferred = Q.defer();
promptly.prompt(text, function(err, value) {
deferred.resolve(value);
});
return deferred.promise;
};
// this option just uses the promise option to prompt for name.
function start1() {
prompter("What is your name?").then(function(name) {
prompter("Your age?").then(function(age) {
console.log("Hello " + name + " (" + age + ")");
});
});
}
// this one uses the nfcall funcitonality to directly call the
// promptly.prompt function (and waits for a callback).
function start2() {
Q.nfcall(promptly.prompt, "What is your name? ")
.then(function(name) {
Q.nfcall(promptly.prompt, "What is your age? ")
.done(function(age) {
console.log("Hello " + name + " (" + age + ")");
});
});
}
//start1();
I feel like the answers here can be added to for anyone seeking alternatives to solving the general problem of getting command line user input from node.
Firstly, I personally feel there is merit to moving towards the ES6 Promises API. Although not natively available yet in Node, there is a great polyfill: https://github.com/jakearchibald/es6-promise.
Secondly, I have come to like an alternative user prompting module: https://github.com/flatiron/prompt
Now assuming there exist methods 'addUserToDb', 'printUser' and 'printError' that in turn return promises, the following example is possible:
var prompt = require('node-prompt');
var Promise = require('es6-promise').Promise;
var promptUser = function(schema) {
return new Promise(resolve, reject) {
prompt.get(schema, function(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
};
};
promptUser(["name", "password"])
.then(addUserToDb)
.then(printUser)
.catch(printError)
I've written many 'scripts' now using this method and have found it very nice to work with and easy to maintain / adapt.
I am writing a node js application. I am using request and cheerio to load a set of URLs and get a bunch of information for the site, now let's assume all I am trying to get is the title:
var urls = {"url_1", "url_2", "url_3",...,"url_n"};
for(var i=0; i<urls.length; i++)
{
getDOMTitle(urls[i],function(error,title){
if(error)
console.log("Error while getting title for " + urls[i]);
else
console.log("The title for " + urls[i] + " is " + title);
});
}
This is how my getDOMTitle method looks:
function getDOMTitle(urlReq,callback)
{
var request = require('request');
var cheerio = require('cheerio');
request({url:urlReq},function(error, response, doc){
var $ = cheerio.load(doc);
if(error)
{
callback(true,null);
}
else
{
$('title', 'head').each(function (i, elem) {
var title = $(this).text();
callback(false,title);
});
}
}
}
In the case where the module throws an uncaught exception, how do I handle that situation?
I have tried adding the following:
process.on('uncaughtException', function (err) {
console.error(err);
console.log("Node NOT Exiting...");
callback(true,null);
});
When I do that, I get an error saying I cannot set the headers once they have been sent. If I remove the callback from the process error handling, I do not see that error but the client spins for a long time because I assume we are never calling the callback.
How can I solve this?
Also, I have read somewhere that you can catch uncaught exceptions at the application level so you don't have to replicate the code to catch it in every method, is that possible? and if it is and the method that threw the exception is expected to callback with some information, how can that be achieved?
Thank you,
To answer your stated question, using an uncaught exception handler as a general error-trapping mechanism is commonly regarded as poor design. It's a false economy to use it to handle anything other than non-recoverable situations where you just need to do some cleanup before exiting.
You've got some problems in your example code. In your for loop, all the callbacks are going to report that they were working with the very last URL in your array because they're all referring to the same copy of i, which will be at its highest value by the time any of them execute. You need to use a helper function or an immediate function invocation to give each callback a private copy of i.
In getDOMTitle the error callback should be callback(error) and the code in your loop should include the returned value in the error message. The success callback should use null as its first parameter, though this is just a matter of convention.
Hope someone can assist with a (simple) async question on node-redis. I'm trying to load a set from a hash in the redis db and then use that populated set further on. Here's the code snippet :-
var redis_client = redis.createClient(REDIS_PORT, REDIS_URL);
redis_client.hgetall(target_hash,function(e,o){
Object.keys(o).forEach(function(target){
// get the "name" from the hash
redis_client.hget(o[target],"name",function(e,o){
if (e){
console.log("Error occurred getting key: " + e);
}
else {
redis_client.sadd("newset",o);
}
});
});
// the following line prints nothing - why ??
redis_client.smembers("newset",redis.print);
When I examine the contents of "newset" in redis it is populated as expected, but at runtime it displayed as empty. I'm sure it's an async issue - any help much appreciated !
hgetall is an asynchronous call: when it receives a reply from the redis server, it will eventually call your callback function (target) { ... }. But within your script, it actually returns immediately. Since hgetall returns very fast, Node will immediately run the next statement, smembers. But at this point the sadd statements haven’t run yet (even if your system is very fast because there hasn’t been a context switch yet).
What you need to do is to make sure smembers isn’t called before all the possible sadd calls have executed. redis_client provides the multi function to allow you to queue up all the sadd calls and run a callback when they’re all done. I haven’t tested this code, but you could try this:
var redis_client = redis.createClient(REDIS_PORT, REDIS_URL);
redis_client.hgetall(target_hash, function(e, o) {
var multi = redis_client.multi();
var keys = Object.keys(o);
var i = 0;
keys.forEach(function (target) {
// get the "name" from the hash
redis_client.hget(o[target], "name", function(e, o) {
i++;
if (e) {
console.log("Error occurred getting key: " + e);
} else {
multi.sadd("newset", o);
}
if (i == keys.length) {
multi.exec(function (err, replies) {
console.log("MULTI got " + replies.length + "replies");
redis_client.smembers("newset", redis.print);
});
}
});
});
});
Some libraries have a built-in equivalent of forEach that allows you to specify a function to be called when the loop is all done. If not, you have to manually keep track of how many callbacks there have been and call smembers after the last one.
You shouldn't use multi unless you need actually need a transaction.
just keep a counter of the transactions and call smembers in the final callback
var redis_client = redis.createClient(REDIS_PORT, REDIS_URL);
var keys = Object.keys(o);
var i = 0;
redis_client.hgetall(target_hash,function(e,o){
Object.keys(o).forEach(function(target){
// get the "name" from the hash
redis_client.hget(o[target],"name",function(e,o){
i++
if (e){
console.log("Error occurred getting key: " + e);
}
else {
redis_client.sadd("newset",o);
if (i == keys.length) {
redis_client.smembers("newset", redis.print);
}
}});