避免回调,并嵌套if语句

时间:2020-03-28 22:46:58

标签: javascript node.js if-statement nested

我如何避免这种混乱??,我想以某种方式压缩代码,关于我最喜欢使用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" });
  }
})

3 个答案:

答案 0 :(得分:2)

首先,就像@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)) {
 // ...
}

答案 1 :(得分:1)

这种代码我最喜欢的样式或模式之一是“早期终止”(我不知道它是否有一个众所周知的名称,但这就是我所说的)。而不是在else中嵌套带有错误路径的Happy Path。我将“错误路径”设置为“提前终止”条件。

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;
}

以及如何使用回调函数并将其转换为Promise函数的示例(异步/等待必需)。

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);
        });
    });
}

答案 2 :(得分:1)

嵌套侧面金字塔可以做一些事情。一种非常普遍的技术是先处理错误,然后再执行常规代码。因此,假设您有:

if (everythingFine) {
  //do normal operation
} else {
  //handle error
}

这是 OK ,但您一旦遇到问题便真的遇到了问题

if (everythingFine) {
  //do operation
  if (operationSuccessful) {
    //do next step
  } else {
    //handle error with operation
  }
} else {
  //handle error
}

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

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

  1. 翻转if的条件以检查是否成功,而不是检查错误
  2. 交换if块和else块。
  3. return添加到if
  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"                   |
+----------------+-----------+-----------------+-----------------------------------------+

相反,您可以只检查主持人是否先执行删除操作,如果他们 do 具有足够的权限,则他们是管理员,然后只需执行一次删除操作即可:

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"是因为我不喜欢长表达式前的布尔NOT:

if (!(destroyerUser.role == "Admin" || destroyerUser.role == "Moderator"))

很容易错过,因为您首先看到OR和两个表达式。幸运的是,对于布尔表达式not (A or B) = not A and not B使用De Morgan's law可以很容易地更改它。因此,我将其更改为该方式。

您可以通过添加新功能来处理所有错误来进一步减少某些代码重复。在所有情况下,除了传递不同的消息外,您都执行相同的操作,因此您可能只需要:

const error = msg => res.json({ success: false, msg });

例如,您可以致电error("User not found")。它并没有真正减少您拥有的代码量,但是这种方式更加一致。另外,如果您决定在错误响应中添加更多内容(例如,发送其他标头,或者您想尝试翻译错误消息),那么您将拥有一个中心位置。

相关问题