【问题标题】:How to fetch response body using fetch in case of HTTP error 422?如果出现 HTTP 错误 422,如何使用 fetch 获取响应正文?
【发布时间】:2020-12-04 02:36:09
【问题描述】:

我在后端有一个 API,它发送状态 201 在成功调用的情况下,如果有 提交数据的任何错误都会发送状态422(无法处理的实体),并带有如下的json响应:

{
  "error": "Some error text here explaining the error"
}

此外,它还会发送404,以防 API 由于某种原因无法在后端工作。

在前端,我使用以下代码获取响应并根据响应状态代码执行成功或错误回调:

fetch("api_url_here", {
    method: 'some_method_here', 
    credentials: "same-origin",
    headers: {
      "Content-type": "application/json; charset=UTF-8",
      'X-CSRF-Token': "some_token_here"
    }
    })
  .then(checkStatus)
  .then(function json(response) {
    return response.json()
  })
  .then(function(resp){
    successCallback(resp)
  })
  .catch(function(error){
    errorCallback(error);
  });
  
//status function used above
checkStatus = (response) => {
  if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
  } else {
    return Promise.reject(new Error(response))
  }
}

successCallback 函数能够在状态代码为201 的情况下以正确的 JSON 格式接收响应,但是当我尝试在errorCallback (status: 422) 中获取错误响应时,它会显示类似这样的内容(在控制台中记录响应):

Error: [object Response]
    at status (grocery-market.js:447)

当我尝试console.log 错误响应,然后像这样将其包装在new Error() 中时,

  checkStatus = (response) => {
    if (response.status >= 200 && response.status < 300) {
      return Promise.resolve(response)
    } else {
      console.log(response.json()) //logging the response beforehand
      return Promise.reject(new Error(response.statusText))
    }
  }

我在控制台中得到以下承诺([[PromiseValue]] 属性实际上包含我需要的错误文本)

有人可以解释为什么这仅在错误情况下发生,即使我在错误和成功情况下都调用response.json()

我怎样才能以干净的方式解决这个问题?

编辑: 我发现如果我在错误响应上创建另一个 json() 承诺,我就能得到正确的错误:

fetch("api_url_here", {
    method: 'some_method_here', 
    credentials: "same-origin",
    headers: {
      "Content-type": "application/json; charset=UTF-8",
      'X-CSRF-Token': "some_token_here"
    }
    })
  .then(checkStatus)
  .then(function json(response) {
    return response.json()
  })
  .then(function(resp){
    successCallback(resp)
  })
  .catch(function(error){
    error.json().then((error) => { //changed here
        errorCallback(error)
      });
  });

但是为什么在错误情况下我必须在响应中调用另一个.json()

【问题讨论】:

  • Response.json() 是异步的,因此您可能希望使 checkStatus 异步并等待响应
  • @SørenEriksen 是的,我明白这一点,但在 checkStatus 中获取响应可能不是最干净的方法。我这样做只是为了事先检查发生了什么。

标签: javascript reactjs http error-handling fetch


【解决方案1】:

简答

因为您对两次解析响应内容的假设是不正确的。在原来的sn-p中,满足错误条件时会跳过then(checkStatus)之后的then

长答案

一般来说,结构良好的promise chain 包括:

  1. 承诺在未来的某个时间兑现拒绝
  2. then 仅在 Promise 履行时运行的处理程序
  3. catch 处理程序仅在拒绝 Promise 时运行
  4. finally(可选)在 Promise 解决(在 2 或 3 中)时运行

2 和 3 中的每个处理程序返回一个 Promise 以启用链接。

接下来,Fetch API 的fetch 方法rejects only on network failures,因此无论响应的status 代码如何,都会调用第一个then 方法。您的第一个处理程序,onFulfilled 回调返回一个已完成或已拒绝的 Promise。

如果完成,它会将控制权传递给链中的下一个 then 方法调用,您可以通过在响应上调用 json 方法来提取 JSON,然后将其作为 Promise 值传递给最后一个 then 方法以在successCallback中使用。

如果被拒绝,控制权将传递给 catch 方法调用,该方法调用会接收值设置为 new Error(response) 的 Promise,然后您会立即将其传递给 errorCallback。因此,后者从 Fetch API 接收到一个Error 的实例,其值为Response 的一个实例。

这正是您看到的记录:Error: [object Response],这是在 Error 的实例上调用 toString 方法的结果。第一部分是构造函数名称,第二部分是内容的字符串标记(形式为[type Constructor])。

怎么办?

由于您的 API 会针对每个可能的用例(201404422)返回 JSON 响应,请将解析后的响应传递给已完成和已拒绝的承诺。另外,请注意,您通过省略 varconstlet 关键字不小心在全局范围内声明了 checkStatus

//mock Response object
const res = {
  status: 200,
  body: "mock",
  async json() {
    const {
      body
    } = this;
    return body;
  }
};

const checkStatus = async (response) => {

  const parsed = await response.json();

  const {
    status
  } = response;

  if (status >= 200 && status < 300) {
    return parsed;
  }

  return Promise.reject(new Error(parsed));
};

const test = () => {

  return checkStatus(res)
    .then(console.log)
    .catch((err) => console.warn(err.message))
    .finally(() => {
      if (res.status === 200) {
        res.status = 422;
        return test();
      }
    });

};

test();

另外,既然你已经使用了 ES6 特性(通过箭头函数的存在来判断),为什么不一直使用 async/await 提供的语法糖:

(() => {
  try {
    const response = await fetch("api_url_here", {
      method: 'some_method_here',
      credentials: "same-origin",
      headers: {
        "Content-type": "application/json; charset=UTF-8",
        'X-CSRF-Token': "some_token_here"
      }
    });

    const parsed = await response.json(); //json method returns a Promise!

    const {
      status
    } = response;

    if (status === 201) {
      return successCallback(parsed);
    }

    throw new Error(parsed);

  } catch (error) {
    return errorCallback(error);
  }
})();

请注意,当您将parsed JSON 内容传递给Error() 构造函数时,会调用抽象的ToString 操作(参见ECMA 规范的step 3a)。

如果消息是对象,除非原始对象有toString方法,否则会得到一个字符串标签,即[object Object],导致错误处理程序无法访问对象的内容:

(() => {

  const obj = { msg : "bang bang" };
  
  const err = new Error(obj);
  
  //will log [object Object]
  console.log(err.message);
  
  obj.toString = function () { return this.msg; };
  
  const errWithToString = new Error(obj);

  //will log "bang bang"
  console.log(errWithToString.message);

})();

相反,如果你 reject 一个 Promise 并将对象作为参数传递,则被拒绝的 Promise 的 [[PromiseValue]] 将是对象本身。

【讨论】:

  • 在你定义的 checkStatus 函数中,你写了const parsed = response.json();。在此语句之前不需要await,在 checkStatus 函数本身之前不需要async。否则 IF 语句中的 return Promise.resolve(parsed) 将在 response.json() 解析之前运行。
  • 我错过了吗?哦,当然,它必须与await,我的错。教训是 - 永远不要使用原始的 sn-ps 作为框架 :) 我什至提到“json 方法返回一个 Promise!”在主要的 sn-p...
  • 另外,我的 sn-p 中的这段代码导致了问题 return Promise.reject(new Error(parsed)); .... 将解析后的响应包装在 new Error 中导致它由于某种原因丢失了 json 消息...刚刚使用return Promise.reject(parsed); 现在工作正常
  • @V_S_X - 这很容易解释:我假设您的 API 出错返回一个包含错误消息的字符串(这是一个有效的 JSON)。按照规范,Error 构造函数接受message。当你调用它时,对消息调用抽象的ToString操作,如果是对象,结果是字符串标签[object Object]
【解决方案2】:

这是因为如果发生错误,则跳过处理程序并执行 catch 处理程序,checkStatus 返回一个被拒绝的承诺并绕过剩余的then 链。

这就是我重构代码的方式。

checkStatus = async (response) => {
    if (response.status >= 200 && response.status < 300) 
      return await response.json()

    throw await response.json()
  }
}


fetch("api_url_here", {
    method: 'some_method_here', 
    credentials: "same-origin",
    headers: {
      "Content-type": "application/json; charset=UTF-8",
      'X-CSRF-Token': "some_token_here"
    }
    })
  .then(checkStatus)
  .then(successCallback)
  .catch(errorCallback);

附:使用async/await 可以使这段代码看起来更好看

【讨论】:

  • in case of an error then handlers are skipped and the catch handler is executed .... 错误是指Promise.reject 还是将响应包装在new Error 中?
  • @V_S_X - 我很确定他们的意思是拒绝承诺(不过,你指出潜在的混淆是正确的,因为承诺拒绝和抛出的错误不是一回事)。
  • @V_S_X 根据我的观察,两者都是。
猜你喜欢
  • 1970-01-01
  • 2016-02-22
  • 2020-02-01
  • 2018-01-30
  • 1970-01-01
  • 2018-07-29
  • 1970-01-01
  • 2016-12-12
  • 2020-07-21
相关资源
最近更新 更多