假设我有以下代码。

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {

  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }

  resolve(numerator / denominator);

 });
}

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


返回的目的是在被拒绝后终止函数的执行,并防止在被拒绝后代码的执行。

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

在这种情况下,它阻止了解析(分子/分母);从执行,这不是严格需要的。但是,最好还是终止执行,以防止将来可能出现陷阱。此外,防止运行不必要的代码是一个很好的实践。

背景

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

Pending -初始状态。我们可以从待定状态转移到其他状态 圆满——成功运营 拒绝—操作失败

当一个承诺被履行或被拒绝时,它将无限期地保持这种状态。因此,拒绝一个已实现的承诺或履行一个已被拒绝的承诺,都不会有效果。

这个例子片段表明,虽然承诺在被拒绝后得到了实现,但它仍然被拒绝。

函数除法(分子,分母){ 返回新的承诺((resolve, reject) => { 如果(分母=== 0){ 拒绝(“不能除0”); } Resolve(分子/分母); }); } 分(5,0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error));

那我们为什么要回去呢?

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

函数除法(分子,分母){ 返回新的承诺((resolve, reject) => { 如果(分母=== 0){ 拒绝(“不能除0”); } console.log(“操作成功”); Resolve(分子/分母); }); } 分(5,0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error));

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

执行完resolve/reject后停止执行:

这是标准的JS控制流。

解析/拒绝后返回:

函数除法(分子,分母){ 返回新的承诺((resolve, reject) => { 如果(分母=== 0){ 拒绝(“不能除0”); 返回; } console.log(“操作成功”); Resolve(分子/分母); }); } 分(5,0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error));

返回resolve / reject -由于回调的返回值被忽略,我们可以通过返回reject/resolve语句来保存一行:

函数除法(分子,分母){ 返回新的承诺((resolve, reject) => { 如果(分母=== 0){ return reject("不能除0"); } console.log(“操作成功”); Resolve(分子/分母); }); } 分(5,0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error));

使用if/else块:

函数除法(分子,分母){ 返回新的承诺((resolve, reject) => { 如果(分母=== 0){ 拒绝(“不能除0”); }其他{ console.log(“操作成功”); Resolve(分子/分母); } }); } 分(5,0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error));

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


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

也就是说,在实际情况下,确保只调用一个是很好的干净实践,在这种情况下确实如此,因为没有进一步的异步/延迟处理。“提前返回”的决定与在函数工作完成时终止函数与继续不相关或不必要的处理没有什么不同。

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


1这个技术上的答案还取决于这样一个事实:在这种情况下,“return”后面的代码即使被省略,也不会导致副作用。JavaScript将愉快地除以0并返回+∞/-∞或NaN。


Ori的回答已经解释了没有必要返回,但这是一个很好的练习。请注意,promise构造函数是抛出安全的,因此它将忽略稍后在路径中传递的抛出异常,基本上你有不容易观察到的副作用。

注意,早期返回在回调中也很常见:

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构造函数是throw安全的,它将异常转换为拒绝,但正如其他人指出的那样——一个promise只解决一次。


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

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

这工作得很好,因为Promise构造函数对任何返回值都不做任何操作,并且在任何情况下都不会解析和拒绝返回任何值。

同样的习语也可以用于另一个答案中显示的回调样式:

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

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


如果你在解决/拒绝后不“返回”,在你想要它停止后可能会发生糟糕的事情(比如页面重定向)。来源:我遇到了这种情况。


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

函数除2(分子,分母){ 如果(分母=== 0){ 回报的承诺。拒绝(“不能除0”); } 返回新的承诺((resolve, reject) => { Resolve(分子/分母); }); } Divide2 (4,0).then((result) => console.log(result), (error) => console.log(error));