如何在 HTTP 错误 422 的情况下使用 fetch 获取响应正文?

V S*_*S X 15 javascript error-handling http fetch reactjs

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

{
  "error": "Some error text here explaining the error"
}
Run Code Online (Sandbox Code Playgroud)

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

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

{
  "error": "Some error text here explaining the error"
}
Run Code Online (Sandbox Code Playgroud)

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

Error: [object Response]
    at status (grocery-market.js:447)
Run Code Online (Sandbox Code Playgroud)

当我console.lognew 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))
    }
  }
Run Code Online (Sandbox Code Playgroud)

我在控制台中得到以下承诺(该[[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){
    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))
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Ole*_*ter 5

简答

因为您对响应内容解析两次的假设是不正确的。在原始代码段中,当满足错误条件时跳过thenafter then(checkStatus)

长答案

一般来说,结构良好的承诺链包括:

  1. 承诺在未来某个时候实现拒绝
  2. then在完成Promise运行的处理程序
  3. catch在拒绝Promise运行的处理程序
  4. finally(可选)在 Promise结算时运行(在 2 或 3 中)

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

接下来,fetchFetch API 的方法仅在网络故障时拒绝,因此then无论status响应代码如何,都会调用第一个方法。您的第一个处理程序,onFulfilled回调返回已完成或拒绝的 Promise。

如果满足,它将控制权传递给then链中的下一个方法调用,在那里您通过调用json响应上的方法提取 JSON,然后将其作为 Promise 值传递then给要在successCallback.

如果被拒绝,则控制将传递给catch方法调用,该调用接收具有设置为该值的 Promise,new Error(response)然后您立即将其传递给errorCallback。因此,后者接收 的实例Error,其值是Response来自 Fetch API的实例。

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

该怎么办?

由于您的 API 为每个可能的用例 ( 201, 404, 422)返回 JSON 响应,因此将解析的响应传递给已完成和已拒绝的承诺。另外请注意,你不小心宣布checkStatus通过省略在全球范围内varconstlet关键字:

//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();
Run Code Online (Sandbox Code Playgroud)


此外,由于您已经使用了 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);
  }
})();
Run Code Online (Sandbox Code Playgroud)

请注意,当您将parsedJSON 内容传递给Error()构造函数时,ToString会调用一个抽象操作(请参阅ECMA 规范的步骤 3a)。

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

(() => {
  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);
  }
})();
Run Code Online (Sandbox Code Playgroud)

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

  • 在您定义的 checkStatus 函数中,您编写了“const parsed = response.json();”。在此语句之前不需要“await”,在 checkStatus 函数本身之前需要“async”吗?否则,IF 语句中的 return Promise.resolve(parsed) 将在解析 `response.json()` 之前运行。 (2认同)