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.
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
Even though I can clearly see my output in console. I can't write them into a file. There is 1 output file and its undefined-03-02-2017.txt which contains single line '15:33 undefined'.
for (i = 0; i < channelcount; i++) {
messages[i] -= messagesOld[i];
console.log(channels[i] + ' ' + messages[i]);
messages[i] = messagesOld[i];
fs.open('logs/' + channels[i] + '.txt', 'r', function (err, fd) {
if (err && err.code == 'ENOENT') {
fs.writeFile('logs/' + channels[i] + '-' + moment().format('MM-DD-YYYY') + '.txt', moment().format('H:mm') + ' ' + messages[i], function (err) { });
} else {
fs.appendFile('logs/' + channels[i] + '-' + moment().format('MM-DD-YYYY') + '.txt', moment().format('H:mm') + ' ' + messages[i] + '\n', function () { });
}
});
}
fs.open() is an async function. So your entire loop will run before the first callback actually gets called. By that time i equals channelCOount + 1 and channels[ channelCount + 1 ] is undefined.
You can either wrap the callback into a closure, use a let if you can use ES6, or use fs.openSync()
#Pyro, you used writeFile incorrectly. Please see syntax
writeFile(file, data[, options], callback)
you have to use like this
fs.writeFile('logs/' + channels[i] + '-' + moment().format('MM-DD-YYYY') + moment().format('H:mm') + '.txt ' , messages[i], function (err) { });
I'm updating my knowledge about JavaScript and I stuck on one lesson task.
I have API that is returning string...
API.workerName = function (worker) {
return worker.firstName + ' ' + worker.lastName;
};
The task is to prefix returning string and not change API, but extend it. I also have to avoid copying & pasting code, because 3rd party code can change. I should re-use it instead.
What I did is change this function after loading API...
API.workerName = function (worker) {
return '(' + worker.position + ') ' + worker.firstName + ' ' + worker.lastName;
};
... but I think I did it wrong.
To extend the method, you should save the old definition and call it from your extension:
API.oldWorkerName = API.workerName;
API.workerName = function(worker) {
return '(' + worker.position + ')' + API.oldWorkerName(worker);
};
Or maybe this is what your lesson is looking for:
API.workerPositionAndName = function(worker) {
return '(' + worker.position + ')' + API.workerName(worker);
};
Another neat way to save the old definition and also make it unavailable to anybody else, would be to do something like this using IIFE to create a closure:
API.workerName = (function() {
var old = API.workerName; // this old version is only available inside your new function
return function(worker) {
return '(' + worker.position + ')' + old(worker);
}
})();
Here's an example:
API = {
workerName: function (worker) {
return worker.firstName + ' ' + worker.lastName;
}
};
API.workerName = (function () {
var old = API.workerName;
return function (worker) {
return '(' + worker.position + ')' + old(worker);
};
})();
alert(API.workerName({firstName: "Joe", lastName: "Blogs", position: "Lackey" }));
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);
}
});
});
});
};
I have a function that defines a variable and I would like to use the value in the next function. Each function has it's own query. I think I either need to combine the queries into one function, of which I don't know how to do or setup a global variable with the first query so it can be referenced in the second function.
I've been reading that global variables and they say they're not a good practice. Can someone help me with how to reuse a variable from one function to the next or combine the two queries else another JS method I should consider?
google.setOnLoadCallback(queryValue1);
function queryValue1 () {
var query2 = new google.visualization.Query('https://spreadsheets.google.com/spreadsheet/tq?range=A2:A17&key=0AhCv9Xu_eRnSdFNhSzNQUFd3b1ZfRHgtQURINFpzeGc&gid=9');
query2.send(function (response) {
if (response.isError()) {
alert('Error in query2: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
var data1 = response.getDataTable();
// fetch the data from range cell (row, column) into the span "bx"
for (var z = 0; z <= 15; z++) {
document.getElementById('a' + (z + 22)).innerHTML = data1.getValue(z, 0);
}
});
}
google.setOnLoadCallback(queryValue3);
function queryValue3 () {
var query3 = new google.visualization.Query('https://spreadsheets.google.com/spreadsheet/tq?range=B2:B17&key=0AhCv9Xu_eRnSdFNhSzNQUFd3b1ZfRHgtQURINFpzeGc&gid=10');
query3.send(function (response) {
if (response.isError()) {
alert('Error in query3: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
var data3 = response.getDataTable();
var m1 = data3.getValue(0, 0);
var red22 = "<span style='color:#ff0000' ";
var yellow22 = "<span style='color:#FF9900' ";
var green22 = "<span style='color:#009900' ";
if (m1 <= 70)
{
m1 = red22;
}
else if (71 === m1 && m1 <= 89)
{
m1 = yellow22;
}
else if (m1 >=90)
{
m1 = green22;
}
console.log ("m1= " + m1)
var m1 = (m1 + a22 + "</span>");
console.log ("m1= " + m1)
});
}
Thank you...
As it is, your making two essentially parallel asynchronous network calls (the Query.send() calls). There's no telling which one is going to return first (or if one will fail), so relying in one callback on data from the other is unreliable and a bad idea, no matter whether you use a global variable or something else.
I think you probably just want to chain the two asynchronous calls, so try something of this shape:
google.setOnLoadCallback(queryValue1);
function queryValue1 () {
var query2 = new google.visualization.Query('https://spreadsheets.google.com/spreadsheet/tq?range=A2:A17&key=0AhCv9Xu_eRnSdFNhSzNQUFd3b1ZfRHgtQURINFpzeGc&gid=9');
query2.send(function (response) {
if (response.isError()) {
alert('Error in query2: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
// Get the data you want to pass to query3 here
// var query3Data = ...
queryValue3(query3Data);
});
}
function queryValue3(passedData) {
var query3 = new google.visualization.Query('https://spreadsheets.google.com/spreadsheet/tq?range=B2:B17&key=0AhCv9Xu_eRnSdFNhSzNQUFd3b1ZfRHgtQURINFpzeGc&gid=10');
query3.send(function (response) {
if (response.isError()) {
alert('Error in query3: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
// Do something with passedData here
// ...
});
}
So don't start the second query until the first one returned.
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).