繁体   English   中英

JavaScript Promises - 拒绝与抛出

[英]JavaScript Promises - reject vs. throw

我已经阅读了几篇关于这个主题的文章,但我仍然不清楚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只是因为它更短,但想知道是否有任何优势。

使用一个和另一个没有任何优势,但是,在特定情况下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!' });

另一个重要的事实是, 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"));

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

我看到的一个帮助我澄清问题的示例是,您可以使用拒绝设置超时函数,例如:

 new Promise((resolve, reject) => { setTimeout(()=>{reject('err msg');console.log('finished')}, 1000); return resolve('ret val') }) .then((o) => console.log("RESOLVED", o)) .catch((o) => console.log("REJECTED", o));

上面不可能用 throw 写。

 try{ new Promise((resolve, reject) => { setTimeout(()=>{throw new Error('err msg')}, 1000); return resolve('ret val') }) .then((o) => console.log("RESOLVED", o)) .catch((o) => console.log("REJECTED", o)); }catch(o){ console.log("IGNORED", o) }

在 OP 的小示例中,无法区分的差异但是在处理更复杂的异步概念时,两者之间的差异可能很大。

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

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

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

考虑以下情况:

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

有一个区别 - 这应该无关紧要 - 其他答案没有涉及,所以:

如果传递给完成处理程序then两罚全中,通过调用返回的承诺then被拒绝,并发生了什么异常。

如果它返回一个被拒绝的承诺,则调用then返回的承诺将被解析为该承诺(并且最终将被拒绝,因为它被解析为的承诺被拒绝),这可能会引入一个额外的异步“滴答”(又一个循环在微任务队列中,用浏览器术语来说)。

但是,任何依赖于这种差异的代码都从根本上被破坏了。 :-) 它不应该对承诺结算的时间那么敏感。

下面是一个例子:

 function usingThrow(val) { return Promise.resolve(val) .then(v => { if (v !== 42) { throw new Error(`${v} is not 42!`); } return v; }); } function usingReject(val) { return Promise.resolve(val) .then(v => { if (v !== 42) { return Promise.reject(new Error(`${v} is not 42!`)); } return v; }); } // The rejection handler on this chain may be called **after** the // rejection handler on the following chain usingReject(1) .then(v => console.log(v)) .catch(e => console.error("Error from usingReject:", e.message)); // The rejection handler on this chain may be called **before** the // rejection handler on the preceding chain usingThrow(2) .then(v => console.log(v)) .catch(e => console.error("Error from usingThrow:", e.message));

如果你运行它,在撰写本文时你会得到:

Error from usingThrow: 2 is not 42!
Error from usingReject: 1 is not 42!

注意顺序。

将其与相同的链进行比较,但都使用usingThrow

 function usingThrow(val) { return Promise.resolve(val) .then(v => { if (v !== 42) { throw new Error(`${v} is not 42!`); } return v; }); } usingThrow(1) .then(v => console.log(v)) .catch(e => console.error("Error from usingThrow:", e.message)); usingThrow(2) .then(v => console.log(v)) .catch(e => console.error("Error from usingThrow:", e.message));

这表明拒绝处理程序以其他顺序运行:

Error from usingThrow: 1 is not 42!
Error from usingThrow: 2 is not 42!

我在上面说“可能”是因为在其他领域有一些工作在其他类似情况下删除了这种不必要的额外勾选,如果涉及的所有承诺都是本机承诺(不仅仅是 thenables)。 (特别是:在async函数中, return await x最初引入了额外的异步滴答,而return x在其他方面相同;ES2020 对其进行了更改,以便如果x是本机承诺,则在没有其他区别的情况下删除额外的滴答。 )

同样,任何对承诺的结算时间如此敏感的代码都已经被破坏了 所以真的不重要/不应该重要。

实际上,正如其他答案所提到的:

除此之外,这主要是风格/偏好问题,因此与大多数风格/偏好一样,与您的团队达成一致(或者您不关心任何一种方式),并保持一致。

一个可以尝试的例子。 只需将 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) })

区别在于三元运算符

  • 您可以使用
return condition ? someData : Promise.reject(new Error('not OK'))
  • 你不能用
return condition ? someData  : throw new Error('not OK')

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM