JavaScript承诺-拒绝与抛出


384

我已经阅读了几篇有关该主题的文章,但是我仍然不清楚Promise.reject与抛出错误之间是否存在区别。例如,

使用Promise.reject

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

使用抛出

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

我倾向于仅使用throw它,因为它更短,但我想知道一个相对于另一个是否有任何优势。


9
两种方法产生完全相同的响应。该.then()处理器捕捉抛出的异常,并自动把它变成一个拒绝承诺。由于我读过抛出异常并不是特别快就可以执行,因此我想返回被拒绝的Promise的执行可能会更快一些,但是如果要知道这一点,则必须在多个现代浏览器中设计一个测试。我个人使用throw是因为我喜欢可读性。
jfriend00

@webduvet与Promises不兼容-它们旨在与throw一起使用。
15

15
缺点throw是,如果它是从异步回调(例如setTimeout)中抛出的,则不会导致被拒绝的承诺。jsfiddle.net/m07van33 @Blondie您的回答是正确的。
凯文B

@joews,这并不意味着它很好;)
webduvet

1
啊,是的。因此,对我的评论的澄清将是:“如果它是在未实现的异步回调中抛出的。我知道有一个例外,我只是不记得那是什么。我也更喜欢使用throw,因为我发现它更具可读性,并允许我reject从参数列表中省略它。
凯文B

Answers:


344

使用一个相对于另一个没有优势,但是,在特定情况下throw无法使用。但是,这些情况可以解决。

每当您进入promise回调时,都可以使用throw。但是,如果您在任何其他异步回调中,则必须使用reject

例如,这不会触发捕获:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

相反,您将面临未解决的承诺和未捕获的异常。在这种情况下,您可能会改为使用reject。但是,您可以通过两种方式解决此问题。

  1. 通过使用超时内的原始Promise拒绝功能:

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. 通过通知超时:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});


54
值得一提的是,您不能使用未承诺的异步回调中的位置throw error,您也不能使用return Promise.reject(err)OP要求我们进行比较的位置。基本上这就是为什么您不应该在Promise中放入异步回调的原因。承诺异步的所有内容,然后您就没有这些限制了。
jfriend00 2015年

9
“但是,如果您正在任何其他类型的回调中”,实际上应该是“但是,如果您正在任何其他类型的异步回调中”。回调可以是同步的(例如与Array#forEach),也可以与之同步,将回调扔进去是可行的。
费利克斯Saparelli

2
@KevinB阅读这些行“在特定情况下,投掷将不起作用。” 和“任何时候在promise回调中,您都可以使用throw。但是,如果您在任何其他异步回调中,则必须使用reject。” 我觉得示例代码片段将显示throw无法正常工作的情况,而是Promise.reject更好的选择。但是,这些片段不会受到这两种选择的影响,并且无论您选择什么,其结果都相同。我想念什么吗?
安舒尔

2
是。如果在setTimeout中使用throw,则不会调用catch。您必须使用reject传递给new Promise(fn)回调的。
凯文

2
@KevinB感谢您的光临。OP给出的示例提到他特别想比较return Promise.reject()throw。他没有提到在rejectstruct中给出的回调new Promise(function(resolve, reject))。因此,虽然您的两个代码片段正确地说明了何时应该使用resolve回调,但是OP的问题不是那样。
安舒尔

200

另一个重要的事实是,reject() 并不像一个终止控制流return语句一样。相反,throw确实终止了控制流。

例:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));


50
好吧,这一点是正确的,但比较是棘手的。因为通常您应该通过写返回被拒绝的承诺return reject(),所以下一行将不会运行。
AZ。

7
您为什么要退货?
lukyer

31
在这种情况下,return reject()这只是一种简写,reject(); return即您想要终止流程。未使用执行程序的返回值(传递给的函数new Promise),因此这是安全的。
费利克斯Saparelli

47

是的,最大的不同是拒绝是一个在承诺被拒绝后执行的回调函数,而throw不能异步使用。如果您选择使用拒绝,您的代码将继续以异步方式正常运行,而throw将优先完成解析器功能(此功能将立即运行)。

我见过的一个可以帮助我弄清楚问题的示例是,您可以设置带有拒绝的Timeout函数,例如:

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

上面不可能用throw编写。

在您的小示例中,两者之间的区别难以区分,但是当处理更复杂的异步概念时,两者之间的区别可能会非常大。


1
这听起来像是一个关键概念,但我不理解它的书面含义。我猜对Promise来说还是太新了。
David Spector

43

TLDR: 当函数有时返回一个 Promise 并抛出异常时,它很难使用。编写异步函数时,最好通过返回被拒绝的承诺来表示失败

您的特定示例混淆了它们之间的一些重要区别:

因为您是 promise链中处理错误,所以抛出的异常会自动转换为拒绝的promise。这可以解释为什么它们似乎可以互换-不能互换。

考虑以下情况:

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

这将是一种反模式,因为您将需要同时支持异步和同步错误情况。它可能看起来像:

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

不好,这正是Promise.reject救援(在全球范围内可用)并有效地与救援区别开的地方throw。重构现在变为:

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

现在,您可以仅使用一种catch()来解决网络故障,使用同步错误检查来缺少令牌:

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }

1
但是,Op的示例总是返回一个承诺。问题是指您应该使用Promise.reject还是throw何时要返回被拒绝的诺言(将跳到下一个诺言.catch())。
马科斯·佩雷拉

@maxwell-我喜欢你的例子。同时,如果在获取时添加一个catch并在其中引发异常,则可以安全地使用try ... catch ...在异常流上没有完美的世界,但是我认为使用一个单一模式很有意义,并且组合模式并不安全(与您的模式对比反模式的类比一致)。
user3053247

1
很好的答案,但我发现这里有一个缺陷-该模式假定所有错误都通过返回Promise.reject处理-对于所有可能从checkCredentials()抛出的意外错误会发生什么?
chenop

1
是的,您说对了@chenop-要捕获那些意外的错误,您需要仍然将它们包装在try / catch中
maxwell

我不明白@maxwell的情况。您是否可以像这样进行结构化设置checkCredentials(x).then(onFulfilled).catch(e) {},并同时catch处理拒绝情况和引发错误情况?
本·惠勒

5

一个例子来尝试。只需将isVersionThrow更改为false即可使用拒绝而不是引发。

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.