0

0

深入理解 Fetch API 响应处理:从 Blob 到文本的正确姿势

DDD

DDD

发布时间:2025-12-05 15:52:31

|

595人浏览过

|

来源于php中文网

原创

深入理解 Fetch API 响应处理:从 Blob 到文本的正确姿势

本教程旨在解决使用 javascript `fetch api` 从服务器获取数据时常见的响应解析问题,特别是当预期为纯文本但实际获取到 `blob` 对象的情况。文章将详细阐述 `fetch api` 响应对象的处理机制,包括 `response.text()`、`response.json()` 和 `response.blob()` 方法的正确使用,以及如何避免“响应体已读取”等常见错误,确保数据能够被准确地解析和利用。

在现代 Web 开发中,Fetch API 已经成为进行网络请求的主流方式。它提供了一种强大而灵活的机制来替代传统的 XMLHttpRequest。然而,在使用 Fetch API 处理服务器响应时,开发者常常会遇到一些挑战,尤其是当服务器返回的数据类型与客户端预期不符,或未能正确解析响应体时。本教程将聚焦于如何正确地从 Fetch 响应中提取所需数据,特别是当服务器返回的是简单文本时。

Fetch API 基础与响应处理

Fetch API 返回一个 Promise,该 Promise 在网络请求完成后解析为一个 Response 对象。Response 对象包含了请求的元数据(如状态码、头部信息等)以及响应体。要获取响应体的内容,我们需要调用 Response 对象上的特定方法,这些方法同样返回 Promise:

  • response.text(): 将响应体解析为纯文本字符串。适用于服务器返回 HTML、XML 或其他纯文本数据。
  • response.json(): 将响应体解析为 JSON 对象。适用于服务器返回 JSON 格式的数据。
  • response.blob(): 将响应体解析为 Blob 对象。适用于处理二进制数据,如图片、文件等。
  • response.arrayBuffer(): 将响应体解析为 ArrayBuffer。适用于更底层的二进制数据操作。
  • response.formData(): 将响应体解析为 FormData 对象。适用于处理表单数据。

选择正确的解析方法是至关重要的一步,它取决于服务器实际返回的数据类型。

常见问题:响应体只可读取一次

在使用 Fetch API 时,一个非常常见的陷阱是尝试多次读取同一个 Response 对象的响应体。Response 对象的响应体是一个可读流,它只能被消费一次。一旦调用了 response.text()、response.json() 或 response.blob() 等方法,响应流就会被读取并关闭,后续再次调用这些方法将会抛出错误,通常是“TypeError: Body has already been used”或类似的提示。

考虑以下错误的示例模式:

fetch(url)
  .then(response => {
    // 错误示范:这里尝试读取响应体并打印,但没有返回其Promise
    console.log(response.text()); // 这会启动读取,但其Promise未被链式处理
    return response.blob(); // 再次尝试读取,可能导致“Body has already been used”
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

正确的做法是确保在 then 块中,你返回了 response 对象上解析方法的 Promise,这样下一个 then 块才能接收到解析后的数据。

fetch(url)
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    // 正确示范:返回response.text()的Promise,以便下一个then()处理其结果
    return response.text(); // 如果预期是文本,使用 text()
  })
  .then(data => {
    // 这里可以直接使用解析后的文本数据
    console.log("接收到的文本数据:", data);
  })
  .catch(error => {
    console.error('Fetch操作失败:', error);
  });

选择正确的响应解析方法:从 Blob 到文本的转换

在实际开发中,如果服务器返回的是一个简单的字符串(例如 "Val is val1"),但客户端的 fetch 请求却意外地得到了一个 Blob 对象,这通常意味着你使用了 response.blob() 来解析一个本应是文本的响应。

原始问题中的 Express 服务器代码如下:

app.get('/getEntry/:key', (req, res) => {
  const entryValue = getEntry(req.params.key); // 假设 getEntry 返回一个字符串
  res.send(entryValue); // Express 的 res.send() 默认会根据内容设置 Content-Type
});

当 entryValue 是一个字符串时,res.send() 通常会设置 Content-Type: text/html; charset=utf-8 或 text/plain。在这种情况下,客户端应该使用 response.text() 来正确解析响应。

Dreamhouse AI
Dreamhouse AI

AI室内设计,快速重新设计你的家,虚拟布置家具

下载

以下是针对上述场景的正确客户端 fetch 代码:

const local_IP = 'YOUR_LOCAL_IP'; // 替换为你的本地IP
const hash = 'Asfa'; // 替换为你的键

fetch(`http://${local_IP}:3000/getEntry/${hash}`, {
    method: 'GET', // 对应 Express 的 app.get(),应使用 GET 方法
    headers: {
      'Accept': 'text/plain, text/html', // 告知服务器客户端接受文本或HTML
    },
    cache: 'default'
  })
  .then(response => {
    if (response.ok) {
      // 服务器返回的是纯文本,所以应该使用 response.text()
      // 注意:response.headers.get('Content-Type') 可以帮助判断实际类型
      console.log('Content-Type:', response.headers.get('Content-Type'));
      return response.text(); // 返回解析为文本的 Promise
    } else {
      throw new Error('Error: ' + response.status + ' ' + response.statusText);
    }
  })
  .then(textData => {
    // textData 现在就是你期望的字符串 "Val is val1"
    console.log("成功获取到期望的字符串:", textData);
  })
  .catch(error => {
    console.error('Fetch操作失败:', error);
  });

关键点总结:

  1. 方法匹配:客户端 fetch 请求的 method 应该与服务器路由定义的方法(app.get, app.post 等)相匹配。如果服务器是 app.get,客户端应使用 method: 'GET'。
  2. 选择正确的解析器:根据服务器返回数据的实际类型(可以通过查看 Response 对象的 headers.get('Content-Type') 来辅助判断),选择 response.text()、response.json() 或 response.blob()。对于简单的字符串,response.text() 是最合适的。
  3. Promise 链式处理:在 then 块中,务必 return 解析方法(如 response.text())返回的 Promise,以确保其结果能够传递到下一个 then 块。
  4. 错误处理:始终检查 response.ok 属性来判断 HTTP 请求是否成功(状态码在 200-299 之间),并在请求失败时抛出错误,以便 catch 块能够捕获并处理。

完整示例与注意事项

为了更好地演示,我们提供一个完整的服务器和客户端代码示例。

Express 服务器 (server.js)

const express = require('express');
const cors = require('cors'); // 引入 cors 中间件
const app = express();
const port = 3000;

// 允许所有来源的跨域请求,实际项目中应配置白名单
app.use(cors());

// 模拟数据存储
const dataStore = {
  'Asfa': 'Val is val1',
  'key2': 'Another value here'
};

// GET 请求路由,返回纯文本
app.get('/getEntry/:key', (req, res) => {
  const key = req.params.key;
  const value = dataStore[key] || `No value found for key: ${key}`;
  // res.send() 会根据内容自动设置 Content-Type,对于字符串通常是 text/html 或 text/plain
  res.send(value);
});

// 启动服务器
app.listen(port, '0.0.0.0', () => { // 监听所有网络接口
  console.log(`Express server running at http://0.0.0.0:${port}`);
});

客户端 JavaScript (client.js 或嵌入 HTML)

document.addEventListener('DOMContentLoaded', () => {
  const fetchButton = document.getElementById('fetchData');
  const resultDiv = document.getElementById('result');
  const ipInput = document.getElementById('localIp');
  const keyInput = document.getElementById('keyToFetch');

  fetchButton.addEventListener('click', async () => {
    const local_IP = ipInput.value;
    const key = keyInput.value;
    const url = `http://${local_IP}:${port}/getEntry/${key}`; // 注意端口号与服务器一致

    resultDiv.textContent = '正在获取数据...';
    resultDiv.style.color = 'black';

    try {
      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Accept': 'text/plain, text/html',
        },
        cache: 'no-cache' // 避免缓存问题
      });

      if (!response.ok) {
        throw new Error(`HTTP 错误! 状态码: ${response.status}`);
      }

      // 获取 Content-Type,辅助判断
      const contentType = response.headers.get('Content-Type');
      console.log('服务器响应 Content-Type:', contentType);

      // 根据 Content-Type 或预期类型选择解析方法
      if (contentType && contentType.includes('application/json')) {
        const jsonData = await response.json();
        resultDiv.textContent = `JSON 数据: ${JSON.stringify(jsonData)}`;
      } else {
        const textData = await response.text();
        resultDiv.textContent = `文本数据: ${textData}`;
      }

      resultDiv.style.color = 'green';

    } catch (error) {
      console.error('Fetch 操作失败:', error);
      resultDiv.textContent = `错误: ${error.message}`;
      resultDiv.style.color = 'red';
    }
  });
});

客户端 HTML (index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fetch API 示例</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        input, button { margin-right: 10px; padding: 8px; }
        #result { margin-top: 20px; padding: 10px; border: 1px solid #ccc; min-height: 50px; }
    </style>
</head>
<body>
    <h1>Fetch API 数据获取</h1>
    <div>
        <label for="localIp">服务器 IP:</label>
        <input type="text" id="localIp" value="127.0.0.1" placeholder="例如: 127.0.0.1">
    </div>
    <div style="margin-top: 10px;">
        <label for="keyToFetch">键名:</label>
        <input type="text" id="keyToFetch" value="Asfa" placeholder="例如: Asfa">
        <button id="fetchData">获取数据</button>
    </div>
    <div id="result"></div>

    <script src="client.js"></script>
</body>
</html>

注意事项:

  • 跨域 (CORS):在上述 Express 服务器中,使用了 cors 中间件来允许跨域请求。在生产环境中,应更精细地配置 cors,只允许受信任的来源访问。
  • 错误处理:健壮的错误处理是任何网络请求的关键。fetch 的 catch 块只会捕获网络错误或 then 块中抛出的错误,而不会捕获 HTTP 错误响应(如 404, 500)。因此,检查 response.ok 是非常重要的。
  • 异步/等待 (async/await):使用 async/await 语法可以使 Promise 链式调用更具可读性,尤其是在处理多个异步操作时。

通过理解 Fetch API 的响应处理机制,并遵循正确的解析方法和 Promise 链式处理原则,开发者可以有效地避免常见问题,确保应用程序能够准确、可靠地与后端服务进行数据交互。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
什么是中间件
什么是中间件

中间件是一种软件组件,充当不兼容组件之间的桥梁,提供额外服务,例如集成异构系统、提供常用服务、提高应用程序性能,以及简化应用程序开发。想了解更多中间件的相关内容,可以阅读本专题下面的文章。

181

2024.05.11

Golang 中间件开发与微服务架构
Golang 中间件开发与微服务架构

本专题系统讲解 Golang 在微服务架构中的中间件开发,包括日志处理、限流与熔断、认证与授权、服务监控、API 网关设计等常见中间件功能的实现。通过实战项目,帮助开发者理解如何使用 Go 编写高效、可扩展的中间件组件,并在微服务环境中进行灵活部署与管理。

225

2025.12.18

json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

452

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

546

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

331

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

81

2025.09.10

Node.js后端开发与Express框架实践
Node.js后端开发与Express框架实践

本专题针对初中级 Node.js 开发者,系统讲解如何使用 Express 框架搭建高性能后端服务。内容包括路由设计、中间件开发、数据库集成、API 安全与异常处理,以及 RESTful API 的设计与优化。通过实际项目演示,帮助开发者快速掌握 Node.js 后端开发流程。

394

2026.02.10

数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

333

2023.10.31

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

4

2026.03.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 5.7万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.3万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号