In this query listed below, my for loop is executing 4 values. In the first alert(id), it is alerting those 4 distinct values. However, in the nested query, the alert statement is printing out the last id value only, 4x with different max(b.id) values. I'm confused. Does anyone know what may be happening? Could a race condition be occurring?
My goal is to place an Ajax call in the nested query, which has input values based on both id and b.id. I am currently doing this, but the value in the ajax call for the "id" is the same for all 4 different calls, which messes up the return data. Thanks.
database.db.transaction(function (tx) {
tx.executeSql('SELECT id, name from programs d', [], function (tx, results) {
for (var i = 0; i < results.rows.length; i++) {
var id = results.rows.item(i)['id'];
var name = results.rows.item(i)['name'];
alert(id);
tx.executeSql('SELECT max(b.id) + 1 max from people b where b.sid = ?',
[id],
function (tx, results) {
lastRecord = results.rows.item(0)['max'];
alert(id + "last rec: " + name);
}
);
}
},
function (event) { alert(event.message); });
As per my comments, you to return a closed function to bind the parameter correctly.
A much simpler example is the following:
Running this produces 4 alerts, all showing 4:
for (i=0;i<4;i++) {
setTimeout( function() { alert(i)}, 1000);
}
Running this produces 4 alerts, showing 0/4, 1/4, 2/4, 3/4.
for (i=0;i<4;i++) {
setTimeout(function(inneri) {
return(
function() {
alert(inneri + "/" + i);
}
);
}(i), 1000);
}
where I've named inneri the value that was preserved upon closure. Note that i, itself is still referring to the outer scope, and thus is 4 (which is what is true at time of execution, since that is the value of i when it dumps out of the for loop, as we're delaying the execution using setTimeout().
The first case is a simpler version of what you're doing, whereas you want the second case.
Rewriting your js (and hoping I get all these ( and {'s in the right place :) ) gives:
database.db.transaction(function (tx) {
tx.executeSql('SELECT id, name from programs d', [], function (tx, results) {
for (var i = 0; i < results.rows.length; i++) {
var id = results.rows.item(i)['id'];
var name = results.rows.item(i)['name'];
alert(id);
tx.executeSql('SELECT max(b.id) + 1 max from people b where b.sid = ?',
[id],
function(innerId) {
return (
function (tx, results) {
lastRecord = results.rows.item(0)['max'];
alert(innerId + "last rec: " + name);
}
);
}(id) //be careful to avoid the ";" here!
);
}
},
function (event) { alert(event.message);
});
Where I have inserted:
function(innerId) {
return (
function (tx, results) {
lastRecord = results.rows.item(0)['max'];
alert(innerId + "last rec: " + name);
}
);
}(id)
in. This function is called immediately via the (id) and returns a function that takes tx and results as arguments and does the appropriate action.
I have checked braces/parenthesis, but don't have a direct way to verify that I didn't make any typos.
Related
I have a TableA containing brands with names, names are for example: brand1, 123, brand2, 999.
I want to select names, create button with id=name and pass the name to function brandOnOff(name), then alert the name I passed.
When I press button "123" or "999" it works correctly. But buttons "brand1" and "brand2" don't work - they alert: [object HTMLButtonElement]. I think I have I problem with "" and '' and I don't know how to fix it...
When I alert(document.getElementById("demo").innerHTML) I get:
<button id="brand1" onclick="brandOnOff(brand1)">brand1</button><button id="123" onclick="brandOnOff(123)">123</button><button id="brand2" onclick="brandOnOff(brand2)">brand2</button><button id="999" onclick="brandOnOff(999)">999</button>
and I think it should be like: ... onclick="brandOnOff("brand1")"... etc --- Quotation-mark then name then Quotation-mark
but when I try to add Quotation-marks there's an error "Unexpected end of input" and I keep messing it up.
Can somebody help me please? I'm stuck :(
Here is the code:
DB.transaction(function (tx) {
tx.executeSql('SELECT * FROM TableA', [], function (tx, rs) {
var brand;
for (i = 0; i < brands; i++)
{
brand = rs.rows.item(i).name;
document.getElementById("demo").innerHTML = document.getElementById("demo").innerHTML + '<button id="' + brand + '" onclick="brandOnOff(' + brand + ')">' + brand + '</button>';
}
}, function (tx, error) {
console.log('SELECT error: ' + error.message);
});
});
function brandOnOff(brandName) {
alert(brandName);
}
Your main issue is caused by trying to use inline event handlers, when these are generally considered obsolete and addEventHandler is universally supported.
You should also split out your logic somewhat into smaller testable units, that separate HTML page generation from database code:
// event handler - passed the clicked element in ev.target
function brandOnOff(ev) {
alert(ev.target.id);
}
// takes an array of brand names and generates a button for each
function buildBrandButtons(brands) {
let demo = document.getElementById('demo');
brands.forEach(brand => {
let button = document.createElement('button');
button.id = brand;
button.textContent = brand;
button.addEventListener('click', brandOnOff);
demo.addChild(button);
});
}
// converts a result set into an array of the specified field's values
function getResultSetField(rs, field) {
let values = [];
for (let i = 0; i < rs.rows.length; ++i) {
values.push(rs.rows.item(i)[field]);
}
return values;
}
// the meat - gets the brand names, builds the buttons
function processBrands(tx, rs) {
let brands = getResultSetField(rs, 'name');
buildBrandButtons(brands);
}
// generic error handler
function selectError(tx, error) {
console.log('SELECT error: ' + error.message);
}
// the actual database work
DB.transaction(tx => {
tx.executeSql('SELECT * FROM TableA', [], processBrands, selectError);
});
This may look like a lot more code, but each part has a specific responsibility, and some of these functions may be re-used later (e.g. selectError, getResultSetField).
NB: no nested quote marks, or indeed any that aren't around a string constant.
I wrote a ~50 lines script to perform housekeeping on MySQL databases. I'm afraid my code exhibits anti-patterns as it rapidly escalates to an unreadable mess for the simple functions it performs.
I'd like some opinions for improving readability.
The full script is at the bottom of this post to give an idea.
Spotlight on the problem
The excessive nesting is caused by patterns like this repeated over and over: (snippet taken from script)
sql.query("show databases")
.then(function(rows) {
for (var r of rows) {
var db = r.Database;
(function(db) {
sql.query("show tables in " + db)
.then(function(rows) {
// [...]
}
})(db);
}
});
I'm nesting one promise under the other within both a for loop and a closure. The loop is needed to iterate across all results from sql.query(), and the closure is necessary to pass the value of db to the lower promise; without the closure, the loop would complete even before the nested promise executes at all, so db would always contain only the last element of the loop, preventing the nested promise from reading each value of db.
Full script
var mysql = require("promise-mysql");
var validator = require("mysql-validator"); // simple library to validate against mysql data types
var ignoreDbs = [ "information_schema" ],
multiplier = 2, // numeric records multiplier to check out-of-range proximity
exitStatus = {'ok': 0, 'nearOutOfRange': 1, 'systemError': 2};
(function() {
var sql,
mysqlHost = "localhost",
mysqlUser = "user",
mysqlPass = "";
mysql.createConnection({
host: mysqlHost,
user: mysqlUser,
password: mysqlPass
}).then(function(connection) {
sql = connection;
})
.then(function() {
sql.query("show databases")
.then(function(rows) {
for (var r of rows) {
var db = r.Database;
if (ignoreDbs.indexOf(db) != -1) continue;
(function(db) {
sql.query("show tables in " + db)
.then(function(rows) {
for (var r of rows) {
var table = r["Tables_in_" + db];
(function(table) {
sql.query("describe " + db + "." + table)
.then(function(rows) {
for (var r of rows) {
(function(r) {
var field = r.Field,
type = r.Type, // eg: decimal(10,2)
query = "select " + field + " from " + db + "." + table + " ";
if (table != "nonce") query += "order by date desc limit 1000";
sql.query(query)
.then(function(rows) {
for (var r of rows) {
var record, err;
// remove decimal part, only integer range is checked
record = Math.trunc(r[field]);
err = validator.check(record * multiplier, type);
if (err) {
console.log(err.message);
process.exit(exitStatus.nearOutOfRange);
}
}
});
})(r);
}
});
})(table);
}
});
})(db);
}
});
})
.then(function() {
// if (sql != null) sql.end(); // may not exit process here: sql connection terminates before async functions above
//process.exit(exitStatus.ok); //
});
})();
Trivia
The purpose of the script is to automatically and periodically monitor if any record stored in any row, table and database in MySQL is approaching the out-of-range limit for its specific data type. Several other processes connected to MySQL continuously insert new numeric data with increasing values and nonces; this script is a central point where to check for such numeric limits. The script would then be attached to Munin for continuous monitoring and alerting.
Update: Revised script
As suggested by #Kqcef I modularized the anonymous functions out of the promise nest, and used let to avoid the explicit nesting of an additional function to preserve variable context.
Still this is excessively verbose, previously I wrote the same script in Bash in about 40 lines, but performance was screaming for a port to nodejs.
"use strict";
var mysql = require("promise-mysql");
var validator = require("mysql-validator"); // a simple library to validate against mysql data types
var ignoreDbs = [ "information_schema" ],
multiplier = 2, // numeric records multiplier to check out-of-range proximity
exitStatus = {'ok': 0, 'nearOutOfRange': 1, 'systemError': 2};
var mysqlHost = "localhost",
mysqlUser = "btc",
mysqlPass = "";
// return array of DBs strings
function getDatabases(sql) {
return sql.query("show databases")
.then(function(rows) {
var dbs = [];
for (var r of rows)
dbs.push(r.Database);
return dbs;
});
}
// return array of tables strings
function getTables(sql, db) {
return sql.query("show tables in " + db)
.then(function(rows) {
var tables = [];
for (var r of rows)
tables.push(r["Tables_in_" + db]);
return tables;
});
}
// return array of descriptions
function getTableDescription(sql, db, table) {
return sql.query("describe " + db + "." + table)
.then(function(rows) {
var descrs = [];
for (var r of rows) {
descrs.push({ 'field': r.Field, // eg: price
'type': r.Type}); // eg: decimal(10,2)
}
return descrs;
});
}
// return err object
function validateRecord(record, type) {
var record, err;
if (typeof record != "number") {
console.log("error: record is not numeric.");
process.exit(exitStatus.systemError);
}
// remove decimal part, only integer range is checked
record = Math.trunc(record);
err = validator.check(record * multiplier, type);
return err;
}
(function() {
var sql;
mysql.createConnection({
host: mysqlHost,
user: mysqlUser,
password: mysqlPass
}).then(function(connection) {
sql = connection;
})
.then(function() {
return getDatabases(sql)
})
.then(function(dbs) {
dbs.forEach(function(db) {
if (ignoreDbs.indexOf(db) != -1) return;
getTables(sql, db)
.then(function(tables) {
tables.forEach(function(table) {
getTableDescription(sql, db, table)
.then(function(descrs) {
descrs.forEach(function(descr) {
let field = descr.field,
type = descr.type,
query = "select " + descr.field + " from " + db + "." + table + " ";
if (table != "nonce") query += "order by date desc limit 1000";
sql.query(query)
.then(function(rows) {
rows.forEach(function(row) {
let err = validateRecord(row[field], type);
if (err) {
console.log(err.message);
process.exit(exitStatus.nearOutOfRange);
}
});
});
});
});
});
});
});
});
/*
.then(function() {
//if (sql != null) sql.end();
//process.exit(exitStatus.ok);
});
*/
})();
I agree with Jaromanda in terms of using let in your for loops to block scope the values and avoid your usage of an immediately-invoked function, which, while totally fine in terms of functionality, is decidedly less readable.
In terms of best practices and avoiding anti-patterns, one of the most important things you can strive for in terms of writing 'good' code is building modularized, reusable blocks of code. As it stands, your code has 5 or 6 anonymous functions that exist nowhere but within your chain of promise callbacks. If you were to declare those as functions outside of that chain, not only does that improve the maintainability of your code (you can test each individual one), but, if their names are clearly indicative of their purposes, would make for a very readable promise chain.
(Updated based on User Question)
Rather than leaving inner functions...
function getTableDescription(sql, db, table) {
return sql.query("describe " + db + "." + table)
.then(function(rows) {
var descrs = [];
for (var r of rows) {
descrs.push({ 'field': r.Field, // eg: price
'type': r.Type}); // eg: decimal(10,2)
}
return descrs;
});
}
...you can easily strip that out so that your code is self-documenting:
function collectDescriptionsFromRows(rows) {
var descriptions = [];
for (var row of rows) {
descriptions.push({'field': row.Field, 'type': row.Type});
}
return descriptions;
}
function getTableDescription(sql, db, table) {
return sql.query("describe " + db + "." + table)
.then(collectDescriptionsFromRows);
}
Also, if you ever find yourself doing data collection from one array to another, it's extremely helpful to get used to using built-in higher order functions (map, filter, reduce). Instead of the collectDescriptionsFromRows I just listed, it could be simplified to:
function collectDescriptionsFromRows(rows) {
return rows.map(row => { 'field': row.Field, 'type': row.Type});
}
Much less verbose, much more readable. Your code and promise-chain will shrink and read more like a step-by-step list of instructions if you continue to extract those anonymous functions in the chain. Anywhere you see function(...there is more extracting to do! You can also do some damage (positively) by extracting all the data you need to begin with and use local logic to boil it down to what you need, rather than making several queries. Hope this helps.
So I am looking in my database right now and there is 3 articles, so the x should represent the number 3 for the for loop later. This code is supposed to add articles to the database if they are freshly scraped. In the titles[] array there is 100 items (scraped from news.google). When I execute the code, it seems like it correctly finds the index # of the titles[] list (which is the 55,68,60 repeats), but it shows up as this: (I don't even want the index # being displayed in console either, I want the title of article to be displayed. I had to shrink some of this code, too long)
55
68
60
55
68
60
55
68
60
55
68
60
55
68
60
55
68
Complete.
Successfully added article: undefinedto the database.
Successfully added article: undefinedto the database.
Successfully added article: undefinedto the database.
Successfully added article: undefinedto the database.
Successfully added article: undefinedto the database.
Successfully added article: undefinedto the database.
And here is my code:
// accessing the database
function DatabaseTime(sourcesDates, timeAdded, links, titles, descriptions) {
sourcesDates = sourcesDates;
links = links;
titles = titles;
descriptions = descriptions;
// put counter so params can access this in it's object scope, use it for the for-loop
// object operator. MEAT OF THE BURGER
var databaseOperation = function (sourcesDates, timeAdded, links, titles, descriptions) {
var scanParams = { TableName: "Rnews" }
// using code to setup for accessing the 2nd list
db.scan(scanParams, function(err, scanData) { // scanData = the 2nd list we are going to work with
//use this array later to hold the unique items
var arrayCheck = [];
for (let i = 0; i < scanData.Items.length; i++) {
arrayCheck.push(scanData.Items[i].title);
}
var index = 0;
var counter = 0;
var x;
for (var i = 0; i < titles.length; i++) {
index = 0;
x = 0;
for (var x = 0; x < arrayCheck.length; x++) {
//if (titles[i] === arrayCheck[x]) {
if (titles.indexOf(arrayCheck[x] === -1)) {
index = titles.indexOf(arrayCheck[x]);
console.log(index);
var autoParams = {
TableName: "Rnews",
Item: {
title: titles[index],
source: sourcesDates[index],
url: links[index],
description: descriptions[index],
lastAddOrUpdated: dbTimeStamp,
timePublish: timeAdded[index]
}
}
Insert(autoParams, titles);
}
}
}
function Insert(autoParams) {
db.put(autoParams, function(err, data) {
if (err) throw err;
console.log("Successfully added article: " + titles[i] + "to the database.");
});
}
console.log("Complete.");
});
};
databaseOperation(sourcesDates, timeAdded, links, titles, descriptions);
}
//// END
DatabaseTime(sourcesDates, timeAdded, links, titles, descriptions);
(printing the index):
You're doing console.log(index); in the first if statement, which prints the numbers.
(undefined problem):
you're trying to access titles[i]. the problem is that neither items nor i are defined here. What do you see in your console?
You're also trying to pass items to the Insert function as the second argument, but the function declaration doesn't include a second argument.
if you want to print the item's name, you could either pass more information to your function like this:
function Insert(autoParams, currentItemsName) {
db.put(autoParams, function(err, data) {
if (err) throw err;
console.log("Successfully added article: " + currentItemsName + " to the database.");
});
and then call it:
Insert(autoParams, titles[i]);
instead of:
Insert(autoParams, titles);
Or you could use the data that is already available to you inside the function and print that, which is the better option in my opinion.
function Insert(autoParams) {
var currentItemsName = autoParams.Item.title
db.put(autoParams, function(err, data) {
if (err) throw err;
console.log("Successfully added article: " + currentItemsName + " to the database.");
});
I have the following bit of code in Node.js.
function homeCallback(reply, twid) {
var c = reply.length;
for (var i = c - 1; i >= 0; i--) {
var isRT;
var tweet_id;
if (reply[i].hasOwnProperty('retweeted_status')) {
tweet_id = reply[i].retweeted_status.id_str;
isRT = true;
} else {
tweet_id = reply[i].id_str;
isRT = false;
}
console.log(tweet_id);
var existsQ = "SELECT * FROM tweets WHERE tweet_id=" + connection.escape(tweet_id);
connection.query(existsQ, function (err, rows) {
console.log(tweet_id);
//need to use tweet_id here
});
}
}
reply is a json response from a call to stauses/home_timeline of Twitter's API, connection is a mysql connection
If there are a couple of tweets in reply with ids of 11 and 12 I get an output like this:
11
12
12
12
Although I expect an output like this:
11
12
11
12
The callback on connection.query() would execute asynchronously while local variable tweet_id scoped in the for-loop might get overwritten by next iteration.
Try to duplicate tweet_id inside a closure/function call.
(function(tid) {
console.log(tid);
var existsQ = "SELECT * FROM tweets WHERE tweet_id=" + connection.escape(tid);
connection.query(existsQ, function (err, rows) {
console.log(tid);
//need to use tweet_id here
});
})(tweet_id);
This is not ideal, but would give you some idea.
I have a list with some items, & a loop in which I check whether each item in the list is found in the database or not (To insert it if it's not found).
function AddItems(tx)
{
for(var i = 0 ; i<itemscounter; i++)
{
itemI=itemsList[i];
tx.executeSql('SELECT * FROM Items Where ItemsTable.ItemId="'+itemI+'"', [], AddItemsToDB, errorCB);
}
}
The problem is that the function waits till it finishes all the iterations then execute the sqlite statement so the itemI value is always equal to itemsList[itemscounter].
So it enters the AddItemsToDB "items counter" times, but always with the same value (the last one in the array).
This is the success Function:
function AddItemsToDB(tx, results)
{
var len = results.rows.length;
alert(itemI); // Always has the same value!
if(len==0)
{
tx.executeSql('INSERT INTO ItemsTable (ItemId) VALUES ('+itemI +')');
}
}
So, I'm wondering if there is some method to pass parameters at the success function itself, or if there is another method to pass the items one by one ?
Yes , there is.Try something like this:
tx.executeSql('SELECT * FROM Items Where ItemsTable.ItemId="'+itemI+'"', [],
(function(itemI){
return function(tx,results){
AddItemsToDB(tx,results,itemI);
};
})(itemI), errorCB);
And modify AddItemsToDB like a:
function AddItemsToDB(tx, results, itemI) {