查看这个超级简单的node.js程序:
var g = { a : 1, b : 2 }
function callBack (key, value) {
console.log("Callback called with key: " + key + "\nAnd value: " + value) ;
}
function doNothing (key, value, cb) {
true ;
console.log(key + ": doing nothing") ;
cb() ;
}
function doLoop () {
for (k in g) {
f = function () {
callBack(k, g[k]) ;
}
doNothing(k, g[k], f) ;
}
}
doLoop() ;
运行时,它将生成以下输出:
a: doing nothing
Callback called with key: a
And value: 1
b: doing nothing
Callback called with key: b
And value: 2
好的。这是有意义的--每次调用回调时,它都有正确的参数。
现在看看这个程序:
var mysql = require('mysql') ;
var dbClient = undefined ;
var db_uri = "mysql://xxx:xxx@127.0.0.1/xxx" ;
var schema = {
redirects : "(id int AUTO_INCREMENT, key VARCHAR(50), url VARCHAR(2048))",
clicks : "(ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, IP VARBINARY(16))"
} ;
function createOnEmpty(err, results, fields, tableName, create_def) {
console.log("createOnEmpty called on " + tableName) ;
if (err) {
console.error(err) ;
process.exit(1) ;
} else {
if (0 == results.length) {
dbClient.query(["create table ", tableName, create_def].join(" "),
function (err, results, fields) {} ) ;
} else {
console.log(tableName + " table already exists.") ;
}
}
console.log("\n\n") ;
}
function setupSchema() {
for (table in schema) {
console.log("Checking for table: " + table) ;
// FIXME: Why does this always seem to pass clicks as tablename?!
dbClient.query(
"show tables LIKE '" + table + "'",
function (err, results, fields) {
createOnEmpty(err, results, fields, table, schema[table])
}
);
}
}
function handleDBConnect(err) {
if (err) {
console.error("ERROR: problem connecting to DB: " + err.code) ;
process.exit(1) ;
} else {
console.log("Connected to database.") ;
// Automatically set up the schema, if the tables don't exist
setupSchema() ;
}
}
function MySQLConnect() {
dbClient = mysql.createConnection(db_uri) ;
dbClient.connect(handleDBConnect) ;
}
MySQLConnect() ;
它的产出如下:
Connected to database.
Checking for table: redirects
Checking for table: clicks
createOnEmpty called on clicks
createOnEmpty called on clicks
尽管变量显然已被切换为“重定向”,但循环似乎两次都将参数“单击”作为参数“表”。
我想我对JavaScript/Node在这里的工作方式肯定有一些根本的误解。
发布于 2015-12-26 19:23:22
要理解这种行为,您需要了解以下两个核心js概念:
假设我们有全局变量a
,输出a
到控制台的log
函数,调用log
两次的主函数,第一次使用超时(异步),第二次只是简单的函数调用。
var a = 42;
function log() {
console.log(`a is ${a}`);
}
function main() {
setTimeout(log, 100);
a = 13;
log();
}
main();
此代码产生以下输出:
a is 13
a is 13
为什么第一次a是13岁?
当您调用setTimeout时,它不会阻塞主js线程100 js,它只是在回调队列中添加日志函数。下一行是a = 13
。由于a
在函数体中没有使用var
关键字声明,因此13被分配给在第一行代码中声明的a
。然后,作为最后一行main
函数的结果,我们得到了第一行输出。现在我们有了空的调用堆栈,代码中没有发生任何其他事情,但是回调队列中仍然有日志函数。在传递100 is之后,当且仅当调用堆栈为空(这就是我们的情况),则可以第二次调用log
函数。它再次记录'a is 13'
,因为a
-s值已经重新分配了。
这是对异步回调如何在javascript中工作的简短解释,这也是createOnEmpty called on clicks
两次的原因。dbClient.query
是异步的,当它第一次被调用时,您的for循环完成了它的执行,而table
值是clicks
。快速和肮脏的解决你的问题将是
for (table in schema) {
console.log("Checking for table: " + table) ;
(function (table) {
dbClient.query("show tables LIKE '" + table + "'",
function (err, results, fields) {
createOnEmpty(err, results, fields, table,
schema[table]) } );
)(table);
}
这个立即调用的函数在范围内的每个循环迭代中都会存储table
值。
发布于 2015-12-26 19:16:31
当调用dbClient.query
的回调时,循环已经完成,将您的(隐式全局) table
变量留在schema
的最后一个键上。
您需要使用使用匿名函数对其进行范围调整。(参见这里的各种答案),或者使用基于回调的迭代,如下所示:
function setupSchema() {
Object.keys(schema).forEach(function (table) {
console.log("Checking for table: " + table) ;
// FIXME: Why does this always seem to pass clicks as tablename?!
dbClient.query(
"show tables LIKE '" + table + "'",
function (err, results, fields) {
createOnEmpty(err, results, fields, table, schema[table])
}
);
});
}
https://stackoverflow.com/questions/34477157
复制