承诺拒绝未触发

3

我对js中的Promise相对较为陌生,对于以下代码块中的问题感到困惑:当服务器返回401未授权时,为什么我的catch函数没有运行?

loginUser(email, password).then((token) => {
  console.log("in then")
  //ipcRenderer.send('login-success', token)
}).catch(err => {
  console.log("in catch")   //not running
})

loginUser函数:

function loginUser(email, password) {
  let body = { email: email, password: password  }

  return fetch('http://localhost:3000/api/v1/sessions', {
    method: 'POST',
    body: JSON.stringify(body),
    headers: { 'Content-Type': 'application/json' }
    }).then(response => {
       return response.json().then(json => {
         console.log(response.ok) // false
         return response.ok ? json : Promise.reject(json)
    }).catch(err => {
      console.error(err)
    })
  })
}

任何帮助都将不胜感激。谢谢。

你的代码有没有运行?你确定它没有执行你的 then 语句吗?在控制台和网络选项卡中看到了什么? - timothyclifford
@timothyclifford 抱歉一开始应该提供更多细节。代码确实执行。当我有意输入错误的电子邮件/密码组合时,在我的控制台中会看到三件事。我看到来自 fetch 的服务器响应,401 未经授权,我看到在 fetch 调用的 catch 中的 console.error(err) 实际的错误对象,以及我看到 console.log 输出"in then",但我期望它是 "in catch"。 - jmtibs
你正在捕获登录用户的错误。在捕获后,如果没有抛出异常,Promise 就被视为“已解决”。 - marzelin
@marzelin 哦,我明白了。现在有意义了。谢谢你。 - jmtibs
.catch(err => {console.error(err)}) 的作用是将一个 rejected promise 转换成 resolved promise,这意味着你永远不会返回一个 rejected promise。如果你想像这样记录日志,那么你需要在 .catch() 处理程序中添加 throw err 以重新抛出错误并保持 promise 被拒绝的状态。此外,401 状态仍然是一个成功的 HTTP 请求。服务器被联系并返回了响应。它并没有被拒绝。因此,你需要在 .then() 处理程序中检查状态,或者明确将非 2xxx 响应更改为一个拒绝。 - jfriend00
3个回答

4

从GitHub的fetch中:

https://github.com/github/fetch/issues/201

仅在无法进行请求时,Fetch API 才会失败。如果可以进行请求,即使存在错误状态,fetch也将成功执行。

因此看起来您的.then(分支将处理401,您需要在此处处理它。

.catch(只有在无法发出请求时才会执行。


谢谢帮忙!在fetch调用中删除额外的catch,让调用函数来捕获它,解决了我的问题! - jmtibs
很高兴听到这个消息,很开心这解决了你的问题。如果你喜欢的话,请将其标记为答案 ;) - timothyclifford

3

Promise.reject()
  .catch(() => console.log("rejection is caught in first `catch`"))
  .then(() => console.log("`catch` returns fulfilled  promise so `then` is executed"))
  .catch(() => console.log("this won't be executed"))


太棒了。解释得非常清晰。保存起来以备将来参考。干杯! - jmtibs

0

我看到这里有几个问题:

首先,从fetch()返回的401响应不会被拒绝。这是一个成功的HTTP请求。它联系了服务器,发送了请求并获得了响应。你收到401状态码的事实取决于你的应用程序如何处理。401状态码不会被拒绝。

来自MDN doc for fetch()的说明:

从fetch()返回的Promise不会在HTTP错误状态下拒绝,即使响应是HTTP 404或500。相反,它将正常解析(ok状态设置为false),并且仅在网络故障或任何阻止请求完成的情况下拒绝。

其次,当你这样做时:

}).catch(err => {
  console.error(err)
})

你正在捕获一个被拒绝的Promise,并处理它将其转化为一个已解决的Promise(就像try/catch阻止抛出的异常一样)。所以,按照你的写法,你的函数永远不会返回一个被拒绝的Promise。如果你想要像那样记录日志,但同时保留被拒绝的Promise,那么你需要重新抛出错误:
}).catch(err => {
  console.error(err)
  throw err;
})

如果您只想在获取到有效数据时返回已解决的 Promise,您可以明确地使 Promise 拒绝其他状态,或者您可以检查 fetch 中的 response.ok 并将其转换为拒绝:
function loginUser(email, password) {
  let body = { email: email, password: password  }

  return fetch('http://localhost:3000/api/v1/sessions', {
    method: 'POST',
    body: JSON.stringify(body),
    headers: { 'Content-Type': 'application/json' }
    }).then(response => {
       // make sure that we actually got data
       if (!response.ok) {
          throw new Error(`No response.ok.  Got http status ${response.status}`);
       }
       return response.json().then(json => {
         console.log(response.ok) // false
         return response.ok ? json : Promise.reject(json)
    }).catch(err => {
      console.error(err);
      throw err;
    });
  })
}

第三,由于您提到的错误是授权错误,因此您应该知道fetch()默认情况下不发送任何cookie,因此如果您依赖cookie进行身份验证,则必须特别配置fetch()请求以使用fetch选项发送cookie:credentials: 'include'

有道理,我对 catch 的工作原理存在根本性的误解,但现在在你和其他人的解释下已经完全明白了。非常感谢! - jmtibs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接