使用 Fetch API 时实现错误处理

Umar Hansa
Umar Hansa

本文演示了使用 Fetch API 时的某些错误处理方法。借助 Fetch API,您可以向远程网络资源发出请求。当您进行远程网络调用时,您的网页可能会出现各种潜在的网络错误。

以下各部分介绍了潜在错误,并介绍了如何编写能够提供合理级别的功能来应对错误和意外网络状况的代码。弹性代码可让用户满意,并为您的网站提供标准服务。

预防潜在的网络连接错误

本部分介绍了以下场景:用户创建了名为 "My Travels.mp4" 的新视频,然后尝试将视频上传到视频共享网站。

使用 Fetch 时,很容易考虑用户成功上传视频的正常路径。不过,还有一些路径不太顺畅,但 Web 开发者必须为此做好规划。此类不理想的路径可能是由用户错误、意外的环境条件或视频分享网站上的 bug 导致的。

用户错误示例

  • 用户上传的是图片文件(例如 JPEG),而不是视频文件。
  • 用户开始上传错误的视频文件。然后,在上传过程中,用户指定要上传的正确视频文件。
  • 用户在视频上传时不小心点击了“取消上传”。

环境变化示例

  • 在视频上传过程中,互联网连接中断。
  • 浏览器会在视频上传期间重新启动。
  • 视频共享网站的服务器在上传视频时重启。

视频共享网站出现错误的示例

  • 视频分享网站无法处理带空格的文件名。它预期使用 "My_Travels.mp4""MyTravels.mp4" 等名称,而不是 "My Travels.mp4"
  • 视频分享网站无法上传超出接受的最大文件大小的视频。
  • 视频共享网站不支持上传视频中的视频编解码器。

这些示例在现实生活中可能发生,也确实发生过。您以前可能遇到过此类示例!我们从前面各个类别中各选一个示例,并讨论以下几点:

  • 如果视频分享服务无法处理给定示例,默认行为是什么?
  • 用户期望在示例中发生什么?
  • 我们可以如何改进此流程?
操作 用户开始上传错误的视频文件。然后,在上传过程中,用户指定要上传的正确视频文件。
默认情况下会发生什么 原始文件会继续在后台上传,同时新文件也会上传。
用户的预期 用户希望原始上传停止,以免浪费额外的互联网带宽。
哪些方面有待改进 JavaScript 会在新文件开始上传之前取消对原始文件的提取请求。
操作 用户在上传视频过程中断开了互联网连接。
默认情况下会发生什么 上传进度条似乎停滞在 50% 水平。最终,Fetch API 会超时,并丢弃上传的数据。当互联网连接恢复后,用户必须重新上传文件。
用户的预期 用户希望在无法上传文件时收到通知,并希望在重新上线后,上传进度达到 50% 时自动恢复上传。
哪些方面有待改进 上传页面会告知用户网络连接问题,并向用户保证,当网络连接恢复后,上传操作会继续。
操作 视频分享网站无法处理带空格的文件名。它希望使用“My_Travels.mp4”或“MyTravels.mp4”等名称,而不是“My Travels.mp4”。
默认情况下会发生什么 用户必须等待上传完成。文件上传完毕且进度条显示“100%”后,进度条会显示“请重试”消息。
用户的预期 用户希望在开始上传之前或至少在上传的前 1 秒内看到文件名限制的提示。
哪些方面有待改进 理想情况下,视频分享服务应支持包含空格的文件名。您也可以在开始上传之前通知用户文件名限制。或者,视频分享服务应拒绝上传并显示详细的错误消息。

使用 Fetch API 处理错误

请注意,以下代码示例使用顶级 await浏览器支持),因为此功能可以简化代码。

当 Fetch API 引发错误时

此示例使用 try/catch语句来捕获 try 块中抛出的任何错误。例如,如果 Fetch API 无法提取指定资源,则会抛出错误。在这样的 catch 代码块中,请注意提供有意义的用户体验。如果向用户显示旋转图标(表示某种进度的常见界面),则您可以在 catch 代码块中执行以下操作:

  1. 从页面中移除旋转图标。
  2. 提供有用的消息,说明出现了什么问题以及用户可以采取哪些措施。
  3. 根据可用的选项,向用户显示“重试”按钮。
  4. 在后台,将错误详情发送到错误跟踪服务或后端。此操作会记录错误,以便在后续阶段进行诊断。
try {
  const response = await fetch('https://website');
} catch (error) {
  // TypeError: Failed to fetch
  console.log('There was an error', error);
}

在后续阶段,在诊断您记录的错误时,您可以编写测试用例,以便在用户发现问题之前捕获此类错误。具体测试可能是单元测试、集成测试或验收测试,具体取决于错误。

当网络状态代码表示错误时

以下代码示例向 HTTP 测试服务发出请求,该服务始终响应 HTTP 状态代码 429 Too Many Requests。有趣的是,响应未到达 catch 代码块。404 状态(以及其他一些状态代码)不会返回网络连接错误,而是可以正常解决。

如需检查 HTTP 状态代码是否成功,您可以使用以下任一选项:

  • 使用 Response.ok 属性确定状态代码是否在 200299 的范围内。
  • 使用 Response.status 属性确定响应是否成功。
  • 使用任何其他元数据(例如 Response.headers)来评估响应是否成功。
let response;

try {
  response = await fetch('https://httpbin.org/status/429');
} catch (error) {
  console.log('There was an error', error);
}

// Uses the 'optional chaining' operator
if (response?.ok) {
  console.log('Use the response here!');
} else {
  console.log(`HTTP Response Code: ${response?.status}`)
}

最佳做法是与贵组织和团队中的人员合作,了解可能出现的 HTTP 响应状态代码。后端开发者、开发者运维团队和服务工程师有时可以提供独特的见解,帮助您发现您可能没有预料到的极端情况。

解析网络响应时出错

此代码示例演示了解析响应正文时可能会出现的另一种错误。Response 接口提供了解析不同类型的数据(例如文本或 JSON)的便捷方法。在以下代码中,系统会向 HTTP 测试服务发出网络请求,该服务会将 HTML 字符串作为响应正文返回。但是,系统会尝试将响应正文解析为 JSON,从而抛出错误。

let json;

try {
  const response = await fetch('https://httpbin.org/html');
  json = await response.json();
} catch (error) {
  if (error instanceof SyntaxError) {
    // Unexpected token < in JSON
    console.log('There was a SyntaxError', error);
  } else {
    console.log('There was an error', error);
  }
}

if (json) {
  console.log('Use the JSON here!', json);
}

您必须准备好代码来接受各种响应格式,并验证意外响应不会破坏用户的网页。

假设以下场景:您有一个返回有效 JSON 响应的远程资源,并且该响应已成功使用 Response.json() 方法解析。服务可能会中断。关闭后,系统会返回 500 Internal Server Error。如果在解析 JSON 期间未使用适当的错误处理技术,则可能会抛出未处理的错误,从而导致用户无法访问网页。

当网络请求必须在完成之前取消时

以下代码示例使用 AbortController 取消正在处理的请求。传输中的请求是指已发起但尚未完成的网络请求。

您可能需要取消正在处理的请求的场景可能因人而异,但最终取决于您的使用情形和环境。以下代码演示了如何将 AbortSignal 传递给 Fetch API。AbortSignal 附加到 AbortController,而 AbortController 包含 abort() 方法,该方法会向浏览器表明应取消网络请求。

const controller = new AbortController();
const signal = controller.signal;

// Cancel the fetch request in 500ms
setTimeout(() => controller.abort(), 500);

try {
  const url = 'https://httpbin.org/delay/1';
  const response = await fetch(url, { signal });
  console.log(response);
} catch (error) {
  // DOMException: The user aborted a request.
  console.log('Error: ', error)
}

总结

处理错误的一个重要方面是定义可能出错的各个部分。对于每种情况,请确保为用户准备了适当的后备方案。关于提取请求,请思考以下问题:

  • 如果目标服务器出现故障,会发生什么情况?
  • 如果提取操作收到意外响应,会怎么样?
  • 如果用户的互联网连接失败,会怎么样?

根据网页的复杂程度,您还可以绘制一个流程图,描述不同场景的功能和界面。