我是否需要在早期解决/拒绝后返回?

Do I need to return after early resolve/reject?

本文关键字:拒绝 返回 解决 是否      更新时间:2023-09-26

假设我有以下代码:

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {
  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }
  resolve(numerator / denominator);
 });
}

如果我的目标是使用reject提前退出,我是否也应该养成立即使用return的习惯?

return的目的是在拒绝后终止函数的执行,并阻止其后代码的执行。

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
      return; // The function execution ends here 
    }
    resolve(numerator / denominator);
  });
}

在这种情况下,它阻止resolve(numerator / denominator);执行,这不是严格需要的。但是,最好还是终止执行,以防止将来可能出现的陷阱。此外,避免不必要地运行代码也是一种很好的做法。

承诺可以是以下三种状态之一:

  1. pending -初始状态。我们可以从待定状态切换到其他状态
  2. completed -操作成功
  3. 拒绝-操作失败

当一个承诺被履行或拒绝时,它将无限期地停留在这个状态(已解决)。因此,拒绝已兑现的承诺或履行已被拒绝的承诺都是无效的。

这个示例片段显示,尽管承诺在被拒绝后被履行,但它仍然被拒绝。

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    resolve(numerator / denominator);
  });
}
divide(5,0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

为什么需要返回呢?

虽然我们不能改变一个已解决的承诺状态,拒绝或解决不会停止函数其余部分的执行。该函数可能包含会产生混淆结果的代码。例如:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    
    console.log('operation succeeded');
    resolve(numerator / denominator);
  });
}
divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

即使函数现在不包含这样的代码,这也可能在将来创建一个陷阱。将来的重构可能会忽略在承诺被拒绝后代码仍在执行的事实,这将很难调试。

在resolve/reject之后停止执行:

这是标准的JS控制流的东西

  • resolve/reject后返回:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
      return;
    }
    console.log('operation succeeded');
    resolve(numerator / denominator);
  });
}
divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

  • 返回resolve/reject -由于回调的返回值被忽略,我们可以通过返回拒绝/解决语句节省一行:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      return reject("Cannot divide by 0");
    }
    console.log('operation succeeded');
    resolve(numerator / denominator);
  });
}
divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

  • 使用if/else块:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    } else {
      console.log('operation succeeded');
      resolve(numerator / denominator);
    }
  });
}
divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

我更喜欢使用return选项之一,因为代码更平坦。

一个常见的习惯用法(可能适合您,也可能不适合您)是将returnreject结合起来,同时拒绝承诺并退出函数,这样包括resolve在内的函数的其余部分就不会被执行。如果你喜欢这种风格,它可以使你的代码更紧凑。

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) return reject("Cannot divide by 0");
                           ^^^^^^^^^^^^^^
    resolve(numerator / denominator);
  });
}

这很好,因为Promise构造函数不做任何返回值,并且在任何情况下resolvereject都不返回任何值。

同样的用法也可以用在另一个答案中所示的回调样式中:

function divide(nom, denom, cb){
  if(denom === 0) return cb(Error("Cannot divide by zero"));
                  ^^^^^^^^^
  cb(null, nom / denom);
} 

同样,这工作得很好,因为调用divide的人不期望它返回任何东西,也不使用返回值做任何事情。

如果您在解析/拒绝之后不"返回",那么在您打算停止之后可能会发生不好的事情(如页面重定向)。来源:我碰到了这个。

技术上这里不需要 1 -因为一个Promise可以被解决或拒绝,排他地且只有一次。第一个Promise结果获胜,随后的每个结果都被忽略。与node风格的回调不同。

也就是说,确保在实际情况下只调用一个是良好的干净实践,并且在这种情况下确实没有进一步的异步/延迟处理。"提前返回"的决定与在任何函数的工作完成时结束它与继续不相关或不必要的处理没有什么不同。

在适当的时间返回(或者使用条件来避免执行"其他"情况)可以减少意外地在无效状态下运行代码或执行不必要的副作用的机会;因此,它使代码更不容易"意外破坏"。


1这个技术上的答案还取决于在这种情况下"return"之后的代码,如果省略它,将不会导致副作用。JavaScript会很高兴除以零并返回+∞/-∞或NaN。

Ori的答案已经解释了return没有必要,但这是一个很好的实践。请注意,promise构造函数是抛出安全的,因此它将忽略稍后在路径中传递的抛出异常,本质上你有一些无法轻易观察到的副作用。

请注意,return在回调中也很常见:

function divide(nom, denom, cb){
     if(denom === 0){
         cb(Error("Cannot divide by zero");
         return; // unlike with promises, missing the return here is a mistake
     }
     cb(null, nom / denom); // this will divide by zero. Since it's a callback.
} 

因此,虽然在承诺中这是一个很好的实践,但在回调中是必需的。关于代码的一些注意事项:

  • 你的用例是假设的,不要在同步操作中使用承诺。
  • 承诺构造函数忽略返回值。如果您返回一个非未定义的值,某些库会发出警告,警告您不要错误地返回该值。大多数人都没那么聪明。
  • promise构造函数是抛出安全的,它会将异常转换为拒绝,但正如其他人指出的那样- promise只解析一次。

在许多情况下,可以单独验证参数并立即使用promise .reject(reason)返回被拒绝的承诺。

function divide2(numerator, denominator) {
  if (denominator === 0) {
    return Promise.reject("Cannot divide by 0");
  }
  
  return new Promise((resolve, reject) => {
    resolve(numerator / denominator);
  });
}
divide2(4, 0).then((result) => console.log(result), (error) => console.log(error));