I'm just starting with Node and I am getting stuck with managing the "callback hell." I have successfully managed to work with the Event emitter in order to fire an event in the main js file from a module, but I am unable to figure out how to work the scoping to fire an event from within a callback in the module. Furthermore, I am having issues calling a prototype function from within the call back in the module.
Specifically, here:
rows.forEach(function(thisRow, index, array) {
myDb.query("SELECT COUNT(a.studentID) as total, m.fName, m.lName, m.id " +
"from `absences` a join `members` m on a.studentID = m.id " +
"where a.aDate>=" + myDb.escape(thisRow['beginDate']) + " and " +
"a.aDate<=" + myDb.escape(thisRow['endDate']) + " and a.aDate<'" + today + "' and m.memGroup = " + myDb.escape(thisRow['orchName']) +
"GROUP BY a.studentID ORDER BY total DESC", function(error, row){
if(row.length > 0) {
retValues.push({"fullName": thisRow.fullName, "shortName": thisRow.shortName, "absences": row});
}
if (index === array.length - 1) {
//This call to this fails because, I believe, it is out of scope.
//How can I access this? OR how can I emit an event here that will
//trigger the listener in the index.js?
this._alertServer;
console.log(retValues);
console.log("Done");
}
});
});
The complete code can be found at:
http://pastebin.com/Gw6kzugk
EDIT - The possible answers above are exactly what you should be looking for. Below is what I ended up doing in my situation. Thanks All!
As explained in the comments, you can't use this inside a callback. You need to capture it outside of the callback, like this:
rows.forEach(function(thisRow, index, array) {
var self = this; // the critical line
myDb.query("SELECT COUNT(a.studentID) as total, m.fName, m.lName, m.id " +
"from `absences` a join `members` m on a.studentID = m.id " +
"where a.aDate>=" + myDb.escape(thisRow['beginDate']) + " and " +
"a.aDate<=" + myDb.escape(thisRow['endDate']) + " and a.aDate<'" + today + "' and m.memGroup = " + myDb.escape(thisRow['orchName']) +
"GROUP BY a.studentID ORDER BY total DESC", function(error, row){
if(row.length > 0) {
retValues.push({"fullName": thisRow.fullName, "shortName": thisRow.shortName, "absences": row});
}
if (index === array.length - 1) {
// Use self here, not this
self._alertServer;
console.log(retValues);
console.log("Done");
}
});
});
Although it may not be the most elegant want to approach this situation, what I ended up doing was passing this in a context function a la the short amount of time I spent programming Android programs.
_getAttendanceBySession = function(err, rows, retValue, context) {
/*
Gets attendance for each session given
err -> Errors returned from last mySQL query
rows -> JS Object of session list
retValue -> string being passed to webserver
context -> 'this'
*/
var tmpHTML;
tmpHTML = retValue;
myDb.getConnection(function(err, conn) {
rows.forEach(function(thisRow, index, array) {
conn.query("SELECT COUNT(a.studentID) as total, m.fName, m.lName, m.id from `absences` a join `members` m on a.studentID = m.id where a.aDate>=" + (myDb.escape(thisRow.beginDate)) + " and a.aDate<=" + (myDb.escape(thisRow.endDate)) + " and a.aDate<'" + today + "' and m.memGroup = " + (myDb.escape(thisRow.orchName)) + " GROUP BY a.studentID ORDER BY total DESC", function(error, row) {
if (row.length > 0) {
tmpHTML = tmpHTML + ("<h3 class='text-center'>" + thisRow.fullName + "</h3><div class='panel-group' id='" + thisRow.shortName + "'>");
row.forEach(function(studentRow, index2, array2) {
var tmpLabel;
if (studentRow.total === 1) {
tmpLabel = 'label-default';
} else if (studentRow.total === 2) {
tmpLabel = 'label-warning';
} else {
tmpLabel = 'label-danger';
}
tmpHTML = tmpHTML + ("<div class='panel panel-default'><div class='panel-heading'><a class='attendance-link panel-title' data-toggle='collapse' data-parent='#" + thisRow.shortName + "' href='#" + studentRow.id + "-details'><span class='label pull-left " + tmpLabel + "'>" + studentRow.total + "</span>" + studentRow.fName + " " + studentRow.lName + "</a></div><div class='panel-body collapse' id='" + studentRow.id + "-details'></div></div>");
if (index2 === array2.length - 1) {
tmpHTML = tmpHTML + "</div>";
}
});
}
if (index === array.length - 1) {
conn.release();
context.emit("send-page", tmpHTML);
}
});
});
});
};
Related
I have been trying to send an email for each expired ticket in the database. Snip of it looks like this:
for (var i = 0; I < rows.length; ++i) {
if (today > new Date(rows[i].end_date)) {
(function(id) {
db.exec('update tickets ' +
'set status="expired" ' +
'where ticket_id= ' + id + ';' +
'insert into changes ' +
'values(' + id + ',' +
'"system",' +
'"ticket expired",' +
'"' + (today.getUTCMonth() + 1) +
'/' + today.getUTCDate() +
'/' + today.getUTCFullYear() +
'");',
function(err) {
if (err) {
console.log(err);
return;
}
console.log(id);
sendAlert(id);
}
);
})(rows[i].ticket_id);
}
}
As you can see I tried to use an anonymous function to keep the data in each call from changing but it still didn't work.
I don't know if I am missing something.
Seems the execution of this task is asynchronous. When you need to iterate over asynchronous calls, the operation sequence is not guaranteed.
Maybe you need some control flow library such Async or Promise. There has some methods to control these flows even in iterations.
how can I concat more rationally first item of array to first of second array and so on? Basically automate console.log here is the code:
$("button#search").on("click", function(){
var inputVal = $("input#text").val();
$.getJSON("https://en.wikipedia.org/w/api.php?action=opensearch&search=" + inputVal +"&limit=5&namespace=0&format=json&callback=?", function(json) {
var itemName = $.each(json[1], function(i, val){
})
var itemDescription = $.each(json[2], function(i, val){
})
var itemLink = $.each(json[3], function(i, val){
})
console.log(itemName[0] + " " + itemDescription[0] + " " + itemLink[0]);
console.log(itemName[1] + " " + itemDescription[1] + " " + itemLink[1]);
console.log(itemName[2] + " " + itemDescription[2] + " " + itemLink[2]);
console.log(itemName[3] + " " + itemDescription[3] + " " + itemLink[3]);
console.log(itemName[4] + " " + itemDescription[4] + " " + itemLink[4]);
})//EOF getJSON
});//EOF button click
I believe this is what you are looking for:
for (var i = 0; i < itemName.length; i++) {
console.log(itemName[i] + " " + itemDescription[i] + " " + itemLink[i]);
}
If arrays have the same length, you could use map
var result = $.map(json[1], function(i, val){
var row = val + " " + json[2][i] + " " + json[3][i];
console.log(row);
return row;
}
Also you can use that result later, e.g.
console.log(result[0]);
Using es6 you can do the following:
(in your getJson callback):
function (json) {
const [value, optionsJ, descriptionsJ, linksJ] = json;
let whatIwant = [];
// choose one to loop through since you know they will all be the same length:
optionsJ.forEach(function (option, index) {
whatIwant.push({option: option, description: descriptionJ[index], link: linksJ[index]});
});
// use whatIwant here**
}
Your new whatIwant array will then contain objects for each set.
This is my ajax:
$("form").on("submit", function () {
var data = {
"action": "test"
};
data = $(this).serialize() + "&" + $.param(data);
$.ajax({
type: "POST",
dataType: "json",
url: "ajax2.php",
data: data,
success: function (data) {
$("#main_content").slideUp("normal",function(){
//$(".the-return").html("<br />JSON: " + data+"<br/>");
for (i = 0; i < data.length; i++) {
$(".the-return").append("<div class='inside_return'>Name:" + data[i].name + "<br/>Id:" + data[i].id + "<br/>Pricing:" + data[i].rate + "<br/>Postcode:" + data[i].postcode+ "<br/>Reputation:" + data[i].reputation+"<br/>Review Plus:" + data[i].plus+"<br/>Review Negative:" + data[i].neg+"<br/><h1>Availability</h1>Week Morning:" + data[i].weekM+"<br/>Week Afternoon:" + data[i].weekA+"<br/>Week Evening:" + data[i].weekE+"<br/>Weekend Morning:" + data[i].endM+"<br/>Weekend Afternoon:" + data[i].endA+"<br/>Week Evening:" + data[i].endE+"</div>");
//alert(data[i].name)
}
});
}
});
return false;
});
Above is my ajax. Now this is returning result from query that sorts by postcode by default.
Now when the result displayed, I want to let the user to sort it out by reputation, review and so on..How do I do that.
Put it in a simple way, I just need to alter the order by clause in the query so that it can sort by user selection. What's the easiest way to do it please?
How can I manipulate below part where it appends the result to a div called -the-return so that it sorts by whatever key user use?: Note-> I'm presenting the result in <div> block and not in table.
$(".the-return").append("<div class='inside_return'>Name:" + data[i].name + "<br/>Id:" + data[i].id + "<br/>Pricing:" + data[i].rate + "<br/>Postcode:" + data[i].postcode+ "<br/>Reputation:" + data[i].reputation+"<br/>Review Plus:" + data[i].plus+"<br/>Review Negative:" + data[i].neg+"<br/><h1>Availability</h1>Week Morning:" + data[i].weekM+"<br/>Week Afternoon:" + data[i].weekA+"<br/>Week Evening:" + data[i].weekE+"<br/>Weekend Morning:" + data[i].endM+"<br/>Weekend Afternoon:" + data[i].endA+"<br/>Week Evening:" + data[i].endE+"</div>");
WHat I tried:
success: function (data) {
//I used a function to sort//
data.sort(function (a, b) {
var retVal = 0;
switch (sortOption) {
case 1:
retVal = a.property > b.property ? 1 : (a.property < b.property ? -1 : 0);
break;
// .... many cases here
}
return retVal;
});
//sort function ends here//
$("#main_content").slideUp("normal", function () {
for (i = 0; i < data.length; i++) {
$(".the-return").append("<div class='inside_return'>Name:" + data[i].name + "<br/>Id:" + data[i].id + "<br/>Pricing:" + data[i].rate + "<br/>Postcode:" + data[i].postcode + "<br/>Reputation:" + data[i].reputation + "<br/>Review Plus:" + data[i].plus + "<br/>Review Negative:" + data[i].neg + "<br/><h1>Availability</h1>Week Morning:" + data[i].weekM + "<br/>Week Afternoon:" + data[i].weekA + "<br/>Week Evening:" + data[i].weekE + "<br/>Weekend Morning:" + data[i].endM + "<br/>Weekend Afternoon:" + data[i].endA + "<br/>Week Evening:" + data[i].endE + "</div>");
}
});
}
so when a user clicks a button, it fire the sorting function..Sadly it doesn't work..Placing the function within success, doesn't perform search function it was doing earlier without any sort. Even if I placed it outside the function , still doesn't work.
To sort an array, you can use Array.prototype.sort.
Without any arguments, it attempts to sort elements alphabetically, but you can pass in a comparing function instead.
The function will receive two arguments and should return less than 0, 0 or greater than 0 to define where argument 1 should be in relation to argument 2.
Your sorting function should look something like this:
data.responseData.sort(function (a, b) {
switch (sortOption) {
case 1:
a = a.name,
b = b.name;
type = "string";
case 2:
a = a.reputation,
b = b.reputation;
type = "numeric";
// etc
}
if (type == "numeric")
{
// numeric comparison
return a > b ? 1 : (a < b ? -1 : 0);
} else if (type == "string") {
// string comparison
return a.localeCompare(b);
}
// etc
return;
});
localeCompare will compare the strings for you :)
my list result display I need to create spaces or group them between my displayed list result I tried using the break tags but they don't work
function GetProductDetails(barcodeId, coords)
{
$.getJSON("api/products/?barcodeId=" + barcodeId + "&latitude=" + coords.latitude + "&longitude=" + coords.longitude)
.done(function (data)
{
$('#result').append(data.message)
console.log(data)
var list = $("#result").append('<ul></ul>').find('ul');
$.each(data.results, function (i, item)
{
if(data.results == null)
{
$('#result').append(data.message)
}
else
{
list.append('<li>ShopName :' + item.retailerName + '</li>');
list.append('<li>Name : ' + item.productName + '</li>');
list.append('<li>Rand :' + item.price + '</li>');
list.append('<li>Distance in Km :' + item.Distance + '</li>');
}
});
$("#result").append(ul);
});
}
list.append('<li><b>ShopName</b><p>' + item.retailerName + '</p></li>');
list.append('<li><b>Name</b><p>' + item.productName + '</p></li>');
list.append('<li><b>Rand</b><p>' + item.price + '</p></li>');
list.append('<li><b>Distance</b><p>' + item.Distance + '</p></li>');
Pretty sure that list is always going to be the first ul in your results box, no matter how many you actually add. Also, your final $("#result").append(ul) is an error because there is no ul variable.
Try this:
var list = document.getElementById('result').appendChild(document.createElement('ul'));
data.results.forEach(function(item) {
list.appendChild(document.createElement('li'))
.appendChild(document.createTextNode("ShopName : "+item.retailerName));
list.appendChild(document.createElement('li'))
.appendChild(document.createTextNode("Name : "+item.productName));
list.appendChild(document.createElement('li'))
.appendChild(document.createTextNode("Rand : "+item.price));
list.appendChild(document.createElement('li'))
.appendChild(document.createTextNode("Distance in Km : "+item.Distance));
});
Vanilla JS may be more verbose to write, but it is immensely easier to understand and use, once you get past that mental block ;) Of course, there's nothing to stop you creating a helper function:
function addLIwithText(ul,text) {
ul.appendChild(document.createElement('li'))
.appendChild(document.createTextNode(text));
}
Then your loop contents become:
addLIwithText(list,"ShopName : "+item.retailerName);
// and so on
I am trying to insert around 58000 rows of a query inside a string. But after the row around 8000 I get a timeout error.
I've already tried to use SetTimeout funcions but it was of no use.
Check the code that I am working on:
function onQuerySuccess(tx, results) {
console.log("Entering onQuerySuccess");
if(results.rows) {
console.log("Rows: " + results.rows.length);
var len = results.rows.length;
if(len > 0) {
store_html(results, 0);
console.log("Finished Reading Rows: " + len);
saveNotes();
console.log("Finished Saving Notes");
} else {
//This should never happen
console.log("No rows.");
}
} else {
alert("No records match selection criteria.");
}
console.log("Leaving openView");
function store_html(results, rows_complete){
rows_complete=store_html_input(results, rows_complete);
console.log("Returning row:" + rows_complete);
if (rows_complete<results.rows.length)
{
setTimeout(store_html(results, rows_complete), 50);
}
}
function store_html_input(results, rows_complete){
for(var i = rows_complete; i < rows_complete+100; i++) {
gpsTextFile = gpsTextFile + results.rows.item(i).section + ' ' + results.rows.item(i).timestamp + ' ' + results.rows.item(i).latitude + ' ' +
results.rows.item(i).longitude + ' ' + results.rows.item(i).acx + ' ' + results.rows.item(i).acy + ' ' +
results.rows.item(i).acz + ' ' + results.rows.item(i).speed;
gpsTextFile = gpsTextFile + "\n\r";
}
return i;
}
So.. I get that "Javascript execution exceeded timeout".
Thank you for any of your help!
Best Regards.
You need to change your setTimeout() to NOT execute the function immediately. Change from this:
setTimeout(store_html(results, rows_complete), 50);
to this:
setTimeout(function() {store_html(results, rows_complete)}, 50);
As you had it before, it was immediately executing store_html(results, rows_complete) and passing the return value from that to `setTimeout() which was not delaying anything. This is a common mistake (2nd one of these problems I've answered today).