如果你使用 fetch() 进行后端 API 调用,你需要阅读此内容

2025-06-10

如果你使用 fetch() 进行后端 API 调用,你需要阅读此内容

自首次推出以来,Fetch API已经成为现代 Web 应用程序获取资源和与 Backend API 交互的事实标准。

虽然与XMLHttpRequest类似,但 fetch 提供了更强大的 API 和更灵活的功能集。它也可以用于window以及 ,并且worker还有像node-fetch这样的库允许它在 Node.js 中使用。基本上,fetch 几乎可以在任何地方、任何环境中使用。

它基于承诺的 API 使得异步加载资源变得非常简单,并且还可以轻松处理更复杂的情况,例如有条件地链接获取其他资源等。

虽然 fetch() 很棒并且确实解决了几乎所有 API 调用的麻烦,但在使用它时(或实际上任何其他方法,如XMLHttpRequestaxios等),我们最终不得不处理很多情况,从不同的错误代码,到网络请求失败的情况,将响应主体解析为 json 或文本,提取或解密错误原因以显示给用户或记录等。

这通常会导致每个后端 API 接口函数都重复大量的代码块。对于很多前端 Web 开发者来说,下面的代码片段看起来非常熟悉:

fetch(`${API_BASE_URL}/api/v1/categories`)
  .then((response) => {
    if ((response.status === 200) || (response.status === 400) || (response.status === 401)) {
      return response.json();
    }
  })
  .then((json) => {
    if (!Object.keys(json).includes('errors')) {
      // handle json.data
    } else if (json.errors[0] === 'Invalid token.') { // in case of error, API returns array of error messages
      // handle error due to invalid token, initiate re-login or something else
    } else {
      // handle any other error status codes
    }
  })
  .catch(() => {
    // handle any other case, like json parse failure or network error
  });

显然,上述函数存在很多错误,但我们能否使其变得更好呢?

对于任何后端 API 方法,都会有表示成功情况的状态代码(200、201 等),以及在失败状态代码(如 401、404、500 等)的情况下表示错误的标准方法。

如果我们可以标准化后端 API 的接口并使用该标准化接口进行 API 调用,则上述代码可以大大简化并且不那么脆弱。

考虑到这一点,我们可以创建一种包装函数,使用 fetch() 包装我们的后端 API 调用,并为我们提供后端 API 调用结果的标准接口,无论成功还是失败。

我在许多前端代码库中使用了类似的函数,它确实有助于简化后端 API 调用并快速添加新方法。

const responseParserTypes = {
  json: (response) => response.json(),
  text: (response) => response.text(),
  blob: (response) => response.blob(),
  formData: (response) => response.formData(),
  arrayBuffer: (response) => response.arrayBuffer(),
};

const parseResponse = (response, type) => {
  if (!Object.keys(responseParserTypes).includes(type)) {
    return null;
  }

  return responseParserTypes[type](response);
};

const fetchHandler = (
  fetchPromise,
  {
    handledStatusCodes = [200],
    parseHandledResponseAs = 'json',
    parseUnhandledResponseAs = 'text',
    getUnhandledResponseMessage = () => 'Error occured',
    getFailureMessage = () => 'Error occured',
  },
) => {
  if (!Object.keys(responseParserTypes).includes(parseHandledResponseAs)) {
    throw new Error(`parseHandledResponseAs shouwld be one of [${Object.keys(responseParserTypes).join(', ')}]`);
  }
  if (!Object.keys(responseParserTypes).includes(parseUnhandledResponseAs)) {
    throw new Error(`parseUnhandledResponseAs shouwld be one of [${Object.keys(responseParserTypes).join(', ')}]`);
  }

  return new Promise((resolve, reject) => {
    fetchPromise
      .then((response) => {
        if (handledStatusCodes.includes(response.status)) {
          const parseResponsePromise = parseResponse(response, parseHandledResponseAs);
          parseResponsePromise
            .then((parsedResponse) => resolve(parsedResponse))
            .catch((e) => reject(getFailureMessage(e)));
        } else {
          const parseResponsePromise = parseResponse(response, parseUnhandledResponseAs);
          parseResponsePromise
            .then((parsedResponse) => reject(getUnhandledResponseMessage(
              response.status,
              parsedResponse,
            )))
            .catch((e) => reject(getFailureMessage(e)));
        }
      })
      .catch((e) => reject(getFailureMessage(e)));
  });
};

export default fetchHandler;

您也可以在https://gist.github.com/SiDevesh/adaf910bc384574b776c370f77b9bedf找到它,将来可能还会有更多更新。

现在让我们看看如何callCategoriesIndexPageItemsLoad使用上述函数简化相同的方法fetchHandler

export const getCategories = fetchHandler(
  fetch(`${API_BASE_URL}/api/v1/categories`),
  {
    handledStatusCodes = [200],
    parseHandledResponseAs = 'json',
    parseUnhandledResponseAs = 'json',
    getUnhandledResponseMessage = (statusCode, parsedResponseBody) => {
      if (statusCode === 401) {
        return 'Looks like you are logged out, redirecting to log in page...';
      } else if (statusCode === 500) {
        return 'Something went wrong, we are looking into it';
      } else {
        return 'Unknown error';
      }
    },
    getFailureMessage = (e) => {
      // return proper error message for other failures,
      // like json parse error or network failure,
      // that can be figured out using the exception argument provided
      return 'Network error occured';
    },
  },
)

通过上述实现,我们将获得每个错误状态代码的正确错误消息以及可以在 UI 中显示的任何其他异常。

此外,这会自动处理响应的解析,因此无需链接response.json()reponse.text()任何其他响应解析调用。

使用此方法获取数据非常简单:

getCategories()
  .then((json) => {
    // handle json.data
  })
  .catch((errorMessage) => {
    // show errorMessage
  });

这应该涵盖很多用例,如果需要在发生故障时执行操作,那么我们不仅可以返回string中的消息,还可以返回包含和或其他内容的消息的对象,然后可以对其进行检查,并执行相应的操作。getUnhandledResponseMessagegetFailureMessagestringstatusCode

就是这样,希望这能帮助您简化后端 API 调用方法。

如果您有更好的方法或知道有帮助的库,请告诉我。

希望这篇文章对您有帮助,如果有帮助的话,请在Twitter上关注我以获取更多类似的文章。

干杯!

鏂囩珷鏉ユ簮锛�https://dev.to/sidevesh/if-you-use-fetch-to-make-backend-api-calls-you-need-to-read-this-1dd7
PREV
Float UI:构建美观 Web 界面的 Tailwind UI 替代方案
NEXT
NodeJS + ESLint + Prettier - 有史以来最简单的设置