I have multiple HTTP requests in a nodejs app that each returns a word of a sentence. The replies will come at different times, so I'm saving them in a dictionary, with the key being the original sentence's word index. Problem is, when I access the request object, I only get the last one.
var completed_requests = 0;
sentence = req.query.sentence;
sentence = "sentence to be translated"
responses=[];
words = sentence.split(" ");
for(j=0;j<words.length;j++){
var word = words[j];
var data={
word:word
};
var options = {
url: 'example.com',
form:data,
index:j
};
request.post(options, function(err,httpResponse,body){
options = options;
if(!err){
responses.push({options.index: body});
completed_requests+=1;
if(completed_requests==words.length){
var a="";
for(var k=0;k<words.length;k++){
a+=responses[k]+" ";
}
res.render('pages/index', { something: a });
}
}
else{
//err
}
});
}
Basically, when I access the object.index object, the object returned isn't the one used for the original request, but the last one (for some reason). How should I resolve this?
When we take a look at how the code is evaluated by JavaScript due to it's async nature in node.js the problem becomes obvious:
For the first word the loop for(j=0;j<words.length;j++){ is executed.
The value of j is assigned to options.index. For the loop run this options.index has now the value 0.
request.post(options, function(err,httpResponse,body){ is executed but the callback handler will be invoked later.
For the first word the loop for(j=0;j<words.length;j++){ is executed.
The value of j is assigned to options.index. options.index has now the value 1.
request.post(options, function(err,httpResponse,body){ is executed but the callback handler will be invoked later.
The problem becomes obvious now since no new options objects are created but the value of j is assigned to options.index in every loop run. When the first callback handler is invoked options.index has the value words.length - 1.
To fix the problem we will wrap creating the options object in a function executeRequest
var completed_requests = 0;
sentence = req.query.sentence;
sentence = "sentence to be translated"
responses=[];
words = sentence.split(" ");
for(j=0;j<words.length;j++){
var word = words[j];
var data={
word:word
};
function executeRequest(url, form, index) {
var options = {
url: url,
form: form,
index: index
};
request.post(options, function(err,httpResponse,body){
// options = options; Superfluous
if(!err){
responses.push({ [index]: body});
completed_requests+=1;
if(completed_requests==words.length){
var a="";
for(var k=0;k<words.length;k++){
a+=responses[k]+" ";
}
res.render('pages/index', { something: a });
}
}
else{
//err
}
});
}
executeRequest('example.com', data, j);
}
A good read about scoping and hoisting in JavaScript can be found here http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html
You need to use an async routine such as forEach or map, also I suggest you read up on the async nature of node to help understand how to handle callbacks for io.
Related
In the code below, I am trying to get a value from a 'nodehun' method called spellSuggestions. In the documentation I'm told the syntax to use this method is as follows: dict.spellSuggestions(w, handleMisspellings);
where w is a list of words and handleMisspellings is a function (which is posted below). I can see the output on the console for handleMisspellings, but for the life of me, I cannot return or find a way to return a variable from this call: [dict.spellSuggestions(w, handleMisspellings);]. After setting a var equal to 'dict.spellSuggestions(w, handleMisspellings);' the return value is undefined. Please help!
var debugFlag = process.argv.indexOf('debug') > -1;
var nodehun = require('./../build/' + (debugFlag ? 'Debug' : 'Release') + '/nodehun');
var fs = require('fs');
var dict = new nodehun(fs.readFileSync(__dirname+'/dictionaries/en_US.aff'),fs.readFileSync(__dirname+'/dictionaries/en_US.dic'));
//var words = ['original', 'roach', 'erasee', 'come', 'consol', 'argumnt', 'gage',
// 'libary', 'lisence', 'principal', 'realy', 'license', 'suprise', 'writting'];
var handleMisspellings = function(err, correct, suggestions, origWord, callback) {
if (err) throw err;
if (correct) {
console.log(origWord + ' is spelled correctly!');
}
else {
console.log(origWord + ' not recognized. Suggestions: ' + suggestions);
}
var value = {
err: err,
correct: correct,
suggestions: suggestions,
origWord: origWord
};
console.log('VALUE+++++: ' + value);
return value;
}
var foo = function(words) {
words.forEach(function(w) {
dict.spellSuggestions(w, handleMisspellings);
some = dict;
console.log(JSON.stringify(some, null, 2));
});
}
module.exports = {
foo: foo
}
Thanks Dave. I eventually discovered the practical use of callback functions. For each method that contained data that I desired to access outside of the method, I declared an individual function to wrap the method. The function accepted two input arguments. The first was the input variable to drive the method call. The second was literally 'callback'. Inside the method, I would perform whatever operation I wanted to package the data into a JSON object before returning any desired data with 'return callback(var)'. In the call of the created wrapper function, I would actually call the function using the input variable of choice to drive the method in the function definition, and pass 'function(return_variable)' as the second argument. This creates a new method in which the desired data may be accessed or even again called back. The final code I desired performs a for loop on each element of a list of words, creates metadata about those words, and appends the unique data for each word to each word in a single array. The final array is a single object which contains all input words, and all data about those words. It required 4 individual functions (one of which was recursive), and a function call. Please see the code snippet of the function described above [doCall]. Note the use of the code begins at the call of 'analyze' [which is commented out here] and works its way up to each previous function declaration. I hope this helps someone else in the future to understand the functional use of 'callbacks'. Please ask if you have any questions, and Thanks again =D.
function doCall(word, callback) {
dict.spellSuggestions(word, function(err, correct, suggestions, origWord) {
if (err) throw err;
// if (correct)
// console.log(origWord + ' is spelled correctly!');
// else
// console.log(origWord + ' not recognized. Suggestions: ' + suggestions);
val = {
err: err,
correct: correct,
origWord: origWord,
suggestions: suggestions
}
return callback(val);
});
}
function addMember(array, index, callback){
doCall(array[index], function(val){
// console.log(val);
// console.log(index);
// console.log(array[index]);
// console.log(val.origWord);
array[val.origWord] = val;
// console.log(array[val.origWord]);
index = index + 1;
return callback(array, index);
});
}
function Loop(array, index, callback) {
addMember(array, index, function(array2, index2){
// console.log(index);
// console.log(index2);
if(index2 === array2.length) {
return callback(array2);
}
else{
Loop(array2, index2, callback);
}
});
}
function analyze(array, index, callback){
Loop(array, index, function(complete_array){
console.log('!!!!!!!!!!!!!!!!!' + complete_array);
return callback(complete_array);
});
}
/*
analyze(words, 0, function(complete_array){
// for(i = 0; i < complete_array.length; i++) {
console.log(complete_array);
// }
});
*/
module.exports = {
analyze
}
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
I'm trying to get the html encoded table row value, returned from the slqLite based logger. As I'm new to node modules I'm stuck at:
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database(':memory:');
var html = '';
module.exports = {
readHtml: function() {
var html = ''; // optional but does not work here as well
db.serialize(function() {
db.each("SELECT rowid AS id, info FROM logger", function(err, row) {
html = html + '<tr><td>' + row.info + '<td><tr>'; << html is growing
console.log('Log: ' + row.info); << working
});
});
console.log(html); // html gets empty here!
return html;
}
}
So have no value returned from:
var sysLog = require('logger');
sysLog.init();
sysLog.write('test string1');
sysLog.write('test string2');
console.log(sysLog.readHtml());
It has to be very simple to be solved ...
node is 6.7
You problem is directly related to a very common issue when starting with JavaScript:
How do I return the response from an asynchronous call?
Which shows the simplest way to receive results of an asynchronous operation, such as db.each is using a callback.
function readHtml()
var html = ''
db.serialize(function() {
db.each(..., function(err, row) {
// this line executes sometime later
// after we already returned from readHtml()
});
});
// this line executes right after the call to db.serialize
// but before db.each calls the callback we give to it.
// At this point, html is '' because we still didn't read any rows
// (they are read asynchronously, sometime later)
return html;
}
readHtml(); // <-- this is always '' because rows are read at a later point
To solve this, you would need a function that will be called with a callback like this:
readHtml(function(html) { // <-- this callback gets called once all rows are read
console.log(html);
});
Your situation also has an additional complication that db.each calls its callback once for every row. By looking at the docs, you can see that db.each accepts an additional complete callback when all rows are read. You can use this callback to signalize reading is done and pass the html results.
Here's how you can define readHtml:
function readHtml(callback) { // pass in a callback to call once all rows are read and all html is accumulated
var html = '';
db.serialize(function() {
// read all the rows and accumulate html as before
db.each("SELECT rowid AS id, info FROM logger", function(err, row) {
html = html + '<tr><td>' + row.info + '<td><tr>';
}, function() {
callback(html); // use the second callback to signal you are done and pass the html back
});
});
}
Hi I have a backbone web app using Jquery and NodeJs/mongo as the server side framework. I'm having problems with making a http get call with a foreah loop and the results of the get call being iteratively added to each row of the loop.
var eventid = this.model.get("_id");
var inPromise = $.get("/registrants/list?eventid="+eventid,null,null,"json").then(
function (result){
var temp;
var finalVal = '';
var tempfinalVal = "";
var loop = 0
percentage = 0;
$.each(result.registrants,function(index,registrant){
temp = JSON.parse(registrant.fields);
for (var key in temp) {
if(key =="Email"){
if(temp[key] != ""){
$.get("/stats/registrant?userid="+temp[key]+"&eventid="+eventid,null,null,"json").then(function(result2){
percentage = (result2.Stats.type ===undefined || result2.Stats.type ==null) ? "0": result2.Stats.type;
finalVal +=percentage+"\n";
}).fail(function(){
percentage = "0";
});
}
}else if(key =="eventid"){
loop++;
finalVal = finalVal.slice(0, - 1);
finalVal +='\n';
}
finalVal +=temp[key] + ',';
}
});
//promises.push(inPromise);
}
).done(function(finalVal){
$("#webcast-download-registrants-tn").attr("href",'data:text/csv;charset=utf-8;filename=registration.csv",'+encodeURIComponent(finalVal));
console.log("DONE");
}).fail(function(){
console.log("fail");
});
// promise.done(function () {
// console.log(" PROMISE DONE");
// });
So I have the loop through a collection and the last item of the docuemnt gets a content froma nother http call and when all is fone it will create a CSV file. The problem is that THE "DONE" text echos firts then the "CALL" text is displayed
Rick, your problem is not the simplest due to :
the need for nested asynchronous gets
the need to build each CSV data row partly synchronously, partly asynchronously.
the need for a mechanism to handle the fulfilment of multiple promises generated in the inner loop.
From what you've tried, I guess you already know that much.
One important thing to note is that you can't rely on for (var key in temp) to deliver properties in any particular order. Only arrays have order.
You might try something like this :
var url = "/stats/registrant",
data = { 'eventid': this.model.get('_id') },
rowTerminator = "\n",
fieldNames = ['firstname','lastname','email','company','score'];
function getScore(email) {
return $.get(url, $.extend({}, data, {'userid':email}), null, "json").then(function(res) {
return res.Stats ? res.Stats.type || 0 : 0;
}, function() {
//ajax failure - assume score == 0
return $.when(0);
});
}
$.get("/registrants/list", data, null, "json").then(function(result) {
var promises = [];//An array in which to accumulate promises of CSV rows
promises.push($.when(fieldNames)); //promise of CSV header row
if(result.registrants) {
$.each(result.registrants, function(index, registrant) {
if(registrant.fields) {
// Synchronously initialize row with firstname, lastname, email and company
// (omitting score for now).
var row = fieldNames.slice(0,-1).map(function(fieldName, i) {
return registrant.fields[fieldName] || '';
});
//`row` remains available to inner functions due to closure
var promise;
if(registrant.fields.Email) {
// Fetch the registrant's score ...
promise = getScore(registrant.fields.Email).then(function(score) {
//... and asynchronously push the score onto row
row.push(score);
return row;
});
} else {
//or synchronously push zero onto row ...
row.push(0);
//... and create a resolved promise
promise = $.when(row);
}
promises.push(promise);//Accumulate promises of CSV data rows (still in array form), in the correct order.
}
});
}
return $.when.apply(null, promises).then(function() {
//Join all the pieces, in nested arrays, together into one long string.
return [].slice.apply(arguments).map(function(row) {
return row.join(); //default glue is ','
}).join(rowTerminator);
});
}).done(function(str) {
$("#webcast-download-registrants-tn").attr("href",'data:text/csv;charset=utf-8;filename=registration.csv",'+encodeURIComponent(str));
console.log("DONE");
}).fail(function() {
console.log("fail");
});
partially tested
See comments in code for explanation and please ask if there's anything you don't follow.
In my chrome extension i was checking for a function which can stop my for loop from processing till it gets a response from content scripts. Sharing the sample code below
function abc() {
chrome.tabs.query({'status': 'complete'}, function(tabArray) {
for (var i = 0, tab; tab = tabArray[i]; i++) {
var currentUrl = tab.url;
var tabId = tab.id;
if (currentUrl.match(otherthing)) {
chrome.tabs.sendRequest(tabId, {'type': 'getrequiredthing'},
function(response) {
if (response.isrequiredthind) {
callfunction(tabId);
}
}
);
}
}
});
}
Here when i get the matching url in else if i'm sending a request to the page for getting some info, if my info is positive i need to callfunction. But here in the for loop tabId is iterating very fastly and even if the response is positive it is calling the callfunction with next(or next) tabId.
Can you give your opinions on solving this keep waiting the for loop this response is received.
Thanks
The problem is that the third argument to sendRequest does not block on the request being ready. By design, JavaScript almost never blocks. This is a Good Thing. Instead, it uses an "event-driven" model.
Another problem is due to lexical scoping: When callfunction is called, tabId has the most recent value, not the value when sendRequest was called. To get around this, you need to create a separate scope for each loop iteration e.g.
for (...) {
var tabId = ...;
if (...) {
(function (localTabId) {
chrome.tabs.SendRequest(..., function (response) {
if (response.isrequiredthind) {
callfunction(localTabId);
}
}
})(tabId);
}
}