我需要在 resolve/reject 之后 return 吗?

Do I need to return after early resolve/reject?

假设我有以下代码。

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目的是在rejection后终止函数的执行,阻止其后代码的执行。

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); 执行,这并不是严格需要的。但是,最好还是终止执行以防止将来可能出现的陷阱。此外,最好避免不必要地使用 运行 代码。

背景

承诺可以处于以下 3 种状态之一:

  1. pending - 初始状态。我们可以从 pending 转到其他状态之一
  2. 已完成 - 操作成功
  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));

那么为什么我们需要return?

虽然我们无法更改已解决的承诺状态,但拒绝或解决不会停止其余功能的执行。该函数可能包含会产生混乱结果的代码。例如:

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

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

在resolve/reject之后停止执行:

这是标准的 JS 控制流内容。

  • Return后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));

  • Return 和 resolve / reject - 由于回调的 return 值被忽略,我们可以通过 returning 保存一行reject/resolve 语句:

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 选项之一,因为代码更简洁。

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

话虽这么说,良好的清洁做法 确保在可行的情况下准确调用一个,在这种情况下确实如此,因为没有进一步的 async/deferred 处理. 决定 "return early" 与在其工作完成后结束任何功能没有什么不同 - 与继续不相关或不必要的处理。

在适当的时间返回(或使用条件判断来避免执行 "other" 情况)减少意外 运行 代码处于无效状态或执行不需要的副作用的可能性;因此,它使代码不太容易出现 'breaking unexpectedly'.


1 这个 技术上 答案还取决于 在这种情况下 代码在 "return" 之后,如果将其省略,不会产生副作用。 JavaScript 将 愉快地 除以零和 return +Infinity/-Infinity 或 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 构造函数 忽略 return 值。如果您 return 一个非未定义的值,一些库会警告您不要在那里 returning 的错误。大多数都没有那么聪明。
  • promise 构造函数是安全的,它将异常转换为拒绝,但正如其他人指出的那样 - promise 解析一次。

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

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

这很好用,因为 Promise 构造函数不对任何 return 值做任何事情,并且在任何情况下 resolvereject return 什么都不做。

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

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

同样,这工作正常,因为调用 divide 的人不希望它 return 任何东西,也不对 return 值做任何事情。

如果您在 resolve/reject 之后不 "return",则可能会在您打算停止后发生坏事(例如页面重定向)。资料来源:我运行进入这个。

在许多情况下,可以单独并立即验证参数 return 被拒绝的承诺 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));