为什么Node.js在这两个用途中显示出不同的参数?
看看这个超级简单的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
好。 这是有道理的 – 每次调用callback,它都有正确的参数。
现在看看这个程序:
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
尽pipevariables已经清楚地被切换到“redirect”,循环似乎也将参数'clicks'作为参数'table'给出。
我想我必须对JavaScript / Node在这里的工作有一些基本的误解。
要理解这个行为,你需要理解这个2个核心的js概念:
- closures
- 事件循环
假设我们有全局variablesa
,输出到控制台的log
函数,以及调用两次log
主函数,第一次有超时(asynchronous),第二次只是简单的函数调用
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
为什么第一次是13?
当你调用setTimeout时,它不会阻塞主js线程100ms,它只是将日志函数添加到callback队列中。 下一行是a = 13
。 因为a
没有在var
关键字的函数体内声明,13分配给a
在第一行代码声明。 然后我们有第一行输出作为main
函数的最后一行的结果。 现在我们有空的调用堆栈,在我们的代码中没有其他事情发生,但是我们在callback队列中仍然有日志function。 经过100ms后,当且仅当callstack为空(这是我们的情况)时,可以再次调用log
function。 它再次logging'a is 13'
,因为-s值已被重新分配。
这是关于asynchronouscallback如何在JavaScript中工作的一个简短的解释,这就是为什么createOnEmpty called on clicks
两次createOnEmpty called on clicks
的原因。 dbClient.query
是asynchronous的,当它第一次被调用的时候,你的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
值
- 看看这个关于eventloop的优秀讨论以及如何在js中使用asynchronous的东西
- 如何closures工作
在调用dbClient.query
的callbackdbClient.query
,循环已经完成,将(隐式全局) table
variables留在schema
的最后一个键上。
您需要使用匿名函数 (请参阅各种答案)或使用基于callback的迭代,如下所示:
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]) } ); }); }