Javascript 简化循环和闭包中的嵌套承诺
我编写了一个大约50行的脚本来在MySQL数据库上执行内务管理。我担心我的代码会表现出反模式,因为它执行的简单函数会迅速升级为无法阅读的混乱状态 我想了解一些提高可读性的意见。Javascript 简化循环和闭包中的嵌套承诺,javascript,loops,promise,nested,closures,Javascript,Loops,Promise,Nested,Closures,我编写了一个大约50行的脚本来在MySQL数据库上执行内务管理。我担心我的代码会表现出反模式,因为它执行的简单函数会迅速升级为无法阅读的混乱状态 我想了解一些提高可读性的意见。 完整的脚本在这篇文章的底部给出了一个想法 关注问题 过度嵌套是由这样反复重复的模式造成的:(取自脚本的片段) 我在for循环和闭包中将一个承诺嵌套在另一个承诺之下。需要循环来迭代来自sql.query()的所有结果,并且需要闭包来将db的值传递给较低的值;如果没有闭包,循环甚至会在嵌套承诺执行之前完成,因此db始终只包含
完整的脚本在这篇文章的底部给出了一个想法 关注问题 过度嵌套是由这样反复重复的模式造成的:(取自脚本的片段) 我在
for
循环和闭包中将一个承诺嵌套在另一个承诺之下。需要循环来迭代来自sql.query()
的所有结果,并且需要闭包来将db
的值传递给较低的值;如果没有闭包,循环甚至会在嵌套承诺执行之前完成,因此db
始终只包含循环的最后一个元素,从而防止嵌套承诺读取db
的每个值
全文
琐事
该脚本的目的是自动定期监视MySQL中存储在任何行、表和数据库中的任何记录是否接近其特定数据类型的超出范围限制。其他几个连接到MySQL的进程不断插入新的数值数据,其值和nonce不断增加;此脚本是检查此类数字限制的中心点。然后,脚本将被附加到Munin上,用于持续监控和警报
更新:修订脚本 正如@Kqcef所建议的那样,我将匿名函数从承诺嵌套中模块化,并使用
let
避免显式嵌套附加函数以保留变量上下文
这仍然过于冗长,之前我在Bash中用大约40行代码编写了相同的脚本,但性能要求为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);
});
*/
})();
我同意Jaromanda的观点,即在for循环中使用
let
,以阻止值的作用域,并避免使用立即调用的函数,该函数虽然在功能上完全没有问题,但显然可读性较差
就最佳实践和避免反模式而言,在编写“好”代码方面,您可以争取的最重要的事情之一是构建模块化的、可重用的代码块。目前,您的代码有5或6个匿名函数,它们只存在于承诺回调链中。如果要将这些函数声明为该链之外的函数,不仅可以提高代码的可维护性(您可以测试每个代码),而且如果它们的名称清楚地指示了它们的用途,那么将形成一个非常可读的承诺链
(根据用户问题更新)
而不是离开内部功能
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;
});
}
…您可以轻松地将其剥离出来,以便您的代码能够自我记录:
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);
}
此外,如果您发现自己正在从一个阵列到另一个阵列进行数据收集,那么习惯使用内置的高阶函数(map、filter、reduce)将非常有帮助。与我刚才列出的collectDescriptionsFromRows
不同,它可以简化为:
function collectDescriptionsFromRows(rows) {
return rows.map(row => { 'field': row.Field, 'type': row.Type});
}
更不冗长,更具可读性。如果继续提取链中的匿名函数,您的代码和承诺链将收缩,阅读起来更像是一个循序渐进的指令列表。任何你看到
功能的地方(
…都有更多的提取工作要做!你也可以造成一些伤害(积极的)通过提取您需要的所有数据,并使用本地逻辑将其归结为您需要的内容,而不是进行多次查询。希望这会有所帮助。我同意Jaromanda的观点,即在for循环中使用let
来阻止值的作用域,并避免使用立即调用的函数,这一点虽然非常好就功能而言,它的可读性明显较低
就最佳实践和避免反模式而言,在编写“好”代码方面,您可以争取的最重要的事情之一是构建模块化、可重用的代码块。目前,您的代码有5或6个匿名函数,它们只存在于承诺回调链中。如果您要将它们声明为Function在这条链之外,这不仅提高了代码的可维护性(您可以测试每个代码),而且,如果它们的名称清楚地表明了它们的用途,这将形成一个非常可读的承诺链
(根据用户问题更新)
而不是离开内部功能
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;
});
}
…您可以轻松地将其剥离出来,以便您的代码能够自我记录:
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);
}
此外,如果您发现自己正在从一个阵列到另一个阵列进行数据采集,那么习惯使用内置的高阶函数(映射、筛选、减少)将非常有帮助。与我刚才列出的collectDescriptions fromRows
不同,它可以简化为:
function collectDescriptionsFromRows(rows) {
return rows.map(row => { 'field': row.Field, 'type': row.Type});
}
更不冗长,更具可读性。如果您继续提取链中的匿名函数,您的代码和承诺链将收缩,阅读起来更像是一个逐步的指令列表。无论您在哪里看到
函数(
…都有更多的提取工作要做!您还可能造成一些损害(积极的)通过提取您需要的所有数据,并使用本地逻辑将其归结为您需要的数据,而不是进行多次查询。希望这有所帮助。我认为这个问题更适合您的主要问题可能是“同时”的数量请求发生。例如,如果有5个数据库,每个数据库有5个表,每个表有5个字段,那么可能会有125个“同时”“从”+db+“+表中选择”+field+”
请求我认为这个问题更适合您的主要问题可能是“同时”的数量请求发生。例如,如果有5个数据库,每个数据库有5个表,每个表有5个字段,则可能会有125个“同时”“从”+db+“+表中选择”+field+”