【问题标题】:JavaScript Promises - reject vs. throwJavaScript Promises - 拒绝与抛出
【发布时间】:2016-01-31 10:27:04
【问题描述】:

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

【问题讨论】:

  • 两种方法产生完全相同的响应。 .then() 处理程序捕获抛出的异常并自动将其转换为被拒绝的承诺。由于我已经读过抛出的异常执行起来并不是特别快,我猜想返回被拒绝的承诺可能会稍微快一点执行,但如果知道这一点很重要,你必须在多个现代浏览器中设计一个测试。我个人使用throw,因为我喜欢可读性。
  • @webduvet 不与 Promises 一起使用 - 它们旨在与 throw 一起使用。
  • throw 的一个缺点是,如果它是从异步回调(例如 setTimeout)中抛出的,它不会导致被拒绝的承诺。 jsfiddle.net/m07van33@Blondie 你的答案是正确的。
  • @KevinB 是的。出于这种原因,我认为最好用 Promises 替换所有异步回调。您可以从 Promisified 超时中抛出:jsbin.com/mebogukele/edit?js,console
  • 啊,真的。因此,对我的评论的澄清是,“如果它是从异步回调中抛出的,没有被承诺。我知道有一个例外,我只是不记得它是什么。我也更喜欢使用 throw 只是因为我发现它更具可读性,并且允许我从我的参数列表中省略 reject 它。

标签: javascript promise


【解决方案1】:

是的,最大的不同是 reject 是一个回调函数,在 promise 被拒绝后执行,而 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 的小例子中,差异无法区分,但在处理更复杂的异步概念时,两者之间的差异可能会很大。

【讨论】:

  • 这听起来像是一个关键概念,但我不明白它是书面的。我猜对 Promises 来说还是太新了。
  • @DavidSpector - 不,我对 Promise 非常熟悉,我也很难理解上面的解释。 :-) 除非它在谈论同样的事情Kevin B 在上面发布了一点点。当然,关于“优先考虑”某事的内容尚不清楚。金发女郎,你想澄清一下吗?
  • 这是不正确的。 throw new Error("o_O") 与 reject(new Error("o_O")) 相同。参考learn-javascript-ru.translate.goog/…
  • OP 是 NOT 询问 Promise 构造函数。他问的是在 .then() 内抛出错误。有两种方法可以在 .then() 中抛出错误 - 使用 throwreturn Promise.reject()两者同步工作
【解决方案2】:

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

【讨论】:

  • 值得一提的是,您不能使用throw error 的非承诺异步回调中的位置,您也不能使用return Promise.reject(err),这是OP 要求我们比较的。这就是为什么你不应该将异步回调放在 Promise 中的原因。承诺所有异步的东西,然后你就没有这些限制了。
  • “但是,如果你在任何其他类型的回调中”实际上应该是“但是,如果你在任何其他类型的 asynchronous 回调中”。回调可以是同步的(例如使用Array#forEach),并且对于那些,将它们扔进去就可以了。
  • @KevinB 阅读这些行“在特定情况下 throw 不起作用。”和“任何时候你在一个promise回调中,你都可以使用throw。但是,如果你在任何其他异步回调中,你必须使用reject。”我感觉示例 sn-ps 将显示throw 不起作用的情况,而Promise.reject 是更好的选择。然而,sn-ps 不受这两个选择中的任何一个的影响,并且无论您选择什么都会给出相同的结果。我错过了什么吗?
  • @KevinB 我的意思是,对于任何 sn-ps,无论您使用的是 throw 还是 Promise.reject,您都会得到完全相同的行为。例如,没有捕获错误的 Snippet 1 将不会捕获它,无论您使用的是 throw 'or nah' 还是使用了 return Promise.reject('or nah')
  • 是的。如果在 setTimeout 中使用 throw,则不会调用 catch。您必须使用传递给new Promise(fn) 回调的reject
【解决方案3】:

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

【讨论】:

  • 这点是正确的,但比较起来很棘手。因为通常你应该通过写return reject()来返回你被拒绝的承诺,所以下一行不会运行。
  • 为什么要退货?
  • 在这种情况下,return reject() 只是reject(); return 的简写,即您想要终止流程。 executor(传递给new Promise的函数)的返回值没有被使用,所以这是安全的。
  • 这个让我绊倒了一段时间。 reject() 没有终止流程有什么好的理由吗?似乎应该这样做。
  • @223seneca reject 只是一个普通的 javascript 函数,和其他函数一样,所以它不能终止流,因为函数通常不能终止它们的调用者。
【解决方案4】:

TLDR: 一个函数有时会返回一个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.
      }

【讨论】:

  • 然而,Op 的例子总是返回一个 promise。问题是当你想要返回一个被拒绝的承诺(一个将跳转到下一个.catch()的承诺)时,你是否应该使用Promise.rejectthrow
  • @maxwell - 我喜欢你的例子。同时,如果在 fetch 上添加一个 catch 并在其中抛出异常,那么您将可以安全地使用 try ... catch ... 异常流没有完美的世界,但我认为使用一个单一模式是有意义的,组合模式是不安全的(与你的模式与反模式类比一致)。
  • 很好的答案,但我在这里发现了一个缺陷 - 这种模式假设所有错误都通过返回 Promise.reject 来处理 - 所有可能从 checkCredentials() 抛出的意外错误会发生什么?跨度>
  • 是的,你是对的@chenop - 要捕获那些你需要在 try/catch 中包装的意外错误
  • 我不明白@maxwell 的情况。难道你不能只是构造它,以便你做checkCredentials(x).then(onFulfilled).catch(e) {},并让catch 处理拒绝情况和抛出的错误情况吗?
【解决方案5】:

一个尝试的例子。只需将 isVersionThrow 更改为 false 以使用拒绝而不是 throw。

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

【讨论】:

    【解决方案6】:

    区别在于三元运算符

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

    【讨论】:

    【解决方案7】:

    有一个区别——应该没关系——其他答案没有涉及,所以:

    如果传递给 then 的履行处理程序抛出,则该调用返回的对 then 的承诺会被抛出的内容拒绝。

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

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

    这是一个例子:

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

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

    usingThrow 出错:2 不是 42! usingReject 错误:1 不是 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));

    这表明拒绝处理程序以另一个顺序运行:

    usingThrow 出错:1 不是 42! usingThrow 出错:2 不是 42!

    我在上面说“可能”是因为在其他类似情况下,在其他类似情况下,如果所有涉及的 Promise 都是原生 Promise(而不仅仅是 thenables),因此在其他领域进行了一些工作,消除了这种不必要的额外刻度。 (具体来说:在async 函数中,return await x 最初引入了一个额外的异步滴答声,而 return x 在其他方面是相同的;ES2020 对其进行了更改,因此如果 x 是一个原生承诺,那么额外的滴答声将被删除没有其他区别。)

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

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

    • Kevin B pointed out 一样,throw 将不起作用,如果您正在回调您在履行处理程序中使用过的其他函数 - 这是大问题
    • 作为lukyer pointed outthrow 突然终止函数,这可能很有用(但您在示例中使用return,它做同样的事情)
    • 作为Vencator pointed out,你不能在条件表达式(? :)中使用throw,至少not for now

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-11
      • 2016-10-26
      • 1970-01-01
      • 2012-01-22
      • 2016-04-12
      • 2011-04-09
      • 2020-10-17
      相关资源
      最近更新 更多