避免回调和嵌套 if 语句



我怎样才能避免这种混乱??, 我想以某种方式缩小代码,关于回调,我最喜欢使用 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
}

您立即开始堆叠越来越多的块。更重要的是,成功的逻辑通常很大,因为你以后需要做更多的事情。错误处理逻辑可能是一行。因此,如果您想知道发生了什么,在出错时,您需要滚动很多。

为避免这种情况,您可以提前退出。转换代码的步骤是相同的:

  1. 将检查是否成功的if的条件翻转为检查错误
  2. 交换if块和else块。
  3. if添加return
  4. 移除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"),例如。它并没有真正减少您拥有的代码量,但这种方式更加一致。此外,如果您决定向错误响应添加更多内容(例如,发送其他标头,或者您想尝试翻译错误消息),那么您有一个中心位置。

最新更新