我怎样才能避免这种混乱??, 我想以某种方式缩小代码,关于回调,我最喜欢使用 javascript 的 await/promise 功能,但对于嵌套,如果我真的不知道该怎么办。关于重构这个混乱的任何提示都会很棒,谢谢。
router.delete("/user/:username", passport.authenticate("jwt", { session: false }), (req, res) => {
var { username = null } = req.params;
if (username != null) {
User.getUserById(req.user.id, (err, destroyerUser) => {
if (err) {
res.json({ success: false, msg: "User not found" });
} else {
if (destroyerUser) {
if (destroyerUser.role == "Admin" || destroyerUser.role == "Moderator") {
User.getUserByUsername(username, (err, user) => {
if (!err) {
if (user) {
if (user._id.toString() != destroyerUser._id.toString()) {
if (destroyerUser.role == "Admin") {
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
} else {
res.json({ success: true, msg: "Successfully deleted user" });
}
})
} else if (destroyerUser.role == "Moderator") {
if (user.role == "Moderator" || user.role == "Admin") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
} else {
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
} else {
res.json({ success: true, msg: "Successfully deleted user" });
}
})
}
}
} else {
res.json({ success: false, msg: "You can't delete yourself" });
}
} else {
res.json({ success: false, msg: "User not found" });
}
} else {
res.json({ success: false, msg: "Error finding user" });
}
})
} else {
res.json({ success: false, msg: "You don't have sufficient permissions" })
}
} else {
res.json({ success: false, msg: "Destroyer user not found" });
}
}
});
} else {
res.json({ success: false, msg: "Username is not valid" });
}
})
好吧,首先,正如@LawrenceCheron所说,您应该首先进行验证,而不是在 else 语句中验证。
而不是写作
if (destroyerUser) {
// Do something
} else {
// Exit
}
你可以写
if (!destroyerUser) {
// Exit
}
其次,您可以将 userId 提取到变量中,而不是链接对象键,即
而不是写作
if (user.role == "Moderator" || user.role == "Admin") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
}
你可以写
const userRole = user.role;
if (userRole == "Moderator" || userRole == "Admin") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
}
最后,复杂的语句,例如
if (user._id.toString() != destroyerUser._id.toString()) {
// ...
}
可以提取到更小的函数
const isSameUserId = (id, sample) => id === sample;
if (!isSameUserId(user._id.toString(), destroyerUser._id.toString()) {
// ...
}
或结合上述内容
const isSameUserId = (id, sample) => id === sample;
const userId = user._id.toString();
const destroyerId = destroyerUser._id.toString();
if (!isSameUserId(userId, destroyerId)) {
// ...
}
对于这种代码,我最喜欢的样式或模式之一是提前终止(我不知道它是否有一个众所周知的名称,但这就是我所说的)。而不是将快乐路径与错误路径嵌套在其他路径中。我将错误路径置于提前终止条件中。
if (!username) {
res.json({ success: false, msg: "Username is not valid" });
return;
}
try {
// Get user a custom function you wrote to make sure it utilizes Promises
let destroyerUser = await getUser(req.user.id);
if (!destroyerUser) {
res.json({ success: false, msg: "Destroyer user not found" });
return;
}
if (destroyerUser.role != "Admin" && destroyerUser.role != "Moderator") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
return;
}
// Continue this pattern all the way down
// Nesting remains minimal, code is easy to follow
// The trick is to invert your condition logic
// then place a return so that all the code below won't execute
// thus early termination when validation fails
}
catch(err) {
res.json({ success: false });
return;
}
还有一个示例,说明如何将回调函数转换为承诺函数(这对于异步/等待是必需的)。
function getUser(userId) {
// Return a promise to the caller that will be resolved or rejected
// in the future. Callers can use Promise then or Await for a result.
return new Promise((resolve, reject) => {
User.getUserById(userId, (err, user) => {
// If there is an error, call reject, otherwise resolve
err ? reject(err) : resolve(user);
});
});
}
您可以使用嵌套的横向金字塔执行一些操作。一种非常通用的技术是先处理错误,然后再执行正常的代码。因此,假设您有:
if (everythingFine) {
//do normal operation
} else {
//handle error
}
这没关系,但一旦你得到,你真的会遇到问题
if (everythingFine) {
//do operation
if (operationSuccessful) {
//do next step
} else {
//handle error with operation
}
} else {
//handle error
}
您立即开始堆叠越来越多的块。更重要的是,成功的逻辑通常很大,因为你以后需要做更多的事情。错误处理逻辑可能是一行。因此,如果您想知道发生了什么,在出错时,您需要滚动很多。
为避免这种情况,您可以提前退出。转换代码的步骤是相同的:
- 将检查是否成功的
if
的条件翻转为检查错误 - 交换
if
块和else
块。 - 向
if
添加return
。 - 移除
else
块并将其压平。
你会得到以下内容
if (!everythingFine) {
//handle error
return;
}
//do operation
if (!operationSuccessful) {
//handle error with operation
return;
}
//do next step
现在你删除嵌套并删除 else 块,因为你只需return
并退出函数。如果您的错误处理是已经存在的throw
语句,则执行,因此您不需要return
.
接下来的事情是你在这里有一些重复的逻辑:
if (destroyerUser.role == "Admin") {
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
} else {
res.json({ success: true, msg: "Successfully deleted user" });
}
})
} else if (destroyerUser.role == "Moderator") {
if (user.role == "Moderator" || user.role == "Admin") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
} else {
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
} else {
res.json({ success: true, msg: "Successfully deleted user" });
}
})
}
}
无论destroyerUser
是管理员还是版主,您都调用同一个deleteByOne
。在这两种情况下,您还会返回相同的成功消息。因此,代码中有六个不同的分支,而您只有三个结果:
+----------------+-----------+-----------------+-----------------------------------------+
| destroyer role | user role | deletion result | operation result |
+----------------+-----------+-----------------+-----------------------------------------+
| Moderator | Moderator | N/A | "You don't have sufficient permissions" |
| Moderator | Admin | N/A | "You don't have sufficient permissions" |
| Moderator | <other> | success | "Successfully deleted user" |
| Moderator | <other> | error | "Error deleting user" |
| Admin | <any> | success | "Successfully deleted user" |
| Admin | <any> | error | "Error deleting user" |
+----------------+-----------+-----------------+-----------------------------------------+
相反,您可以检查版主是否先进行删除,如果他们确实有足够的权限,他们是管理员,那么只需执行一次删除:
if (destroyerUser.role == "Moderator" && (user.role == "Moderator" || user.role == "Admin")) {
res.json({ success: false, msg: "You don't have sufficient permissions" })
return;
}
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
return;
}
res.json({ success: true, msg: "Successfully deleted user" });
})
因此,如果我们应用这两件事:
- 反转逻辑,提早退出
- 删除重复的代码
然后你的代码看起来像这样:
router.delete("/user/:username", passport.authenticate("jwt", { session: false }), (req, res) => {
var { username = null } = req.params;
if (username == null) {
res.json({ success: false, msg: "Username is not valid" });
return;
}
User.getUserById(req.user.id, (err, destroyerUser) => {
if (err) {
res.json({ success: false, msg: "User not found" });
return;
}
if (!destroyerUser) {
res.json({ success: false, msg: "Destroyer user not found" });
return;
}
if (destroyerUser.role !== "Admin" && destroyerUser.role !== "Moderator") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
return;
}
User.getUserByUsername(username, (err, user) => {
if (err) {
res.json({ success: false, msg: "Error finding user" });
return;
}
if (!user) {
res.json({ success: false, msg: "User not found" });
return;
}
if (user._id.toString() === destroyerUser._id.toString()) {
res.json({ success: false, msg: "You can't delete yourself" });
return;
}
if (destroyerUser.role == "Moderator" && (user.role == "Moderator" || user.role == "Admin")) {
res.json({ success: false, msg: "You don't have sufficient permissions" })
return;
}
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
return;
}
res.json({ success: true, msg: "Successfully deleted user" });
})
})
});
})
一个注意事项 - 我已经将条件destroyerUser.role == "Admin" || destroyerUser.role == "Moderator"
转换为destroyerUser.role !== "Admin" && destroyerUser.role !== "Moderator"
这是因为我不喜欢长表达式前面的布尔值:
if (!(destroyerUser.role == "Admin" || destroyerUser.role == "Moderator"))
这很容易错过,因为您首先看到 OR 和两个表达式。幸运的是,使用德摩根定律对布尔表达式not (A or B) = not A and not B
很容易改变。因此,我如何将其更改为该。
您可以通过添加新函数来处理所有错误来进一步减少一些代码重复。在所有情况下,除了传递不同的消息外,您都做同样的事情,因此您可以:
const error = msg => res.json({ success: false, msg });
因此,您可以调用error("User not found")
,例如。它并没有真正减少您拥有的代码量,但这种方式更加一致。此外,如果您决定向错误响应添加更多内容(例如,发送其他标头,或者您想尝试翻译错误消息),那么您有一个中心位置。