
1. 项目概述为什么今天还要学Fetch如果你是一名前端开发者或者正在学习JavaScript那么“网络请求”这个概念你一定绕不开。从早期的XMLHttpRequest到后来的jQuery.ajax再到如今几乎成为现代JavaScript异步请求代名词的fetch我们与服务器交互的方式一直在演进。今天我们就来深入聊聊fetch的基础用法。你可能会想这都什么年代了fetch不是早就有了吗没错但根据我这些年的观察很多开发者包括一些有一定经验的对fetch的理解和使用仍然停留在“能用就行”的层面对其背后的设计理念、错误处理机制以及一些高级特性知之甚少。这就像你有一辆性能车却只会用D挡开一样浪费了太多潜力。fetchAPI提供了一个更强大、更灵活、基于Promise的接口用于获取资源包括跨网络。它取代了老旧且笨拙的XMLHttpRequest成为了现代Web开发的标配。但它的“简单”只是表象其设计哲学和细节处理恰恰是区分普通使用者和精通者的关键。本系列的第一篇我们将从最基础、最核心的用法开始帮你建立一个坚实、正确的认知起点避免日后踩坑。2. Fetch API的核心设计哲学与基础结构2.1 从“回调地狱”到“Promise链”在fetch出现之前我们主要依赖XMLHttpRequest或第三方库如jQuery。XMLHttpRequest基于事件和回调函数代码结构容易陷入所谓的“回调地狱”嵌套层级深可读性和可维护性都较差。fetch的设计核心是Promise。Promise是ES6引入的用于处理异步操作的标准化对象它代表一个尚未完成但预期将来会完成的操作。fetch()函数调用后会立即返回一个Promise对象。这个Promise在请求完成无论成功或失败后会进入“已敲定”状态并触发相应的回调.then()或.catch()。这种链式调用的方式让异步代码的流程变得清晰、线性更符合人类的同步思维习惯。这也是fetchAPI最根本的进步之一。2.2 Fetch请求的生命周期与Response对象一个完整的fetch请求其生命周期可以简化为以下几步调用fetch(url, options)发起请求。返回Promise这个Promise会在网络请求完成收到HTTP响应头时被解决resolve。注意这里“完成”指的是HTTP事务完成而不是响应体被完全读取。处理Response对象Promise解决后会得到一个Response对象。这个对象包含了响应的所有元信息状态码、头信息等但不直接包含响应体。解析响应体你需要调用Response对象上的方法来解析响应体例如.json()、.text()、.blob()等。这些方法同样返回一个Promise因为读取响应体本身也是一个异步操作。获取最终数据解析响应体的Promise解决后你才能拿到最终的数据如JSON对象、文本字符串等。这里有一个至关重要的细节fetch只在网络故障或请求被阻止时才会拒绝reject返回的Promise。对于HTTP错误状态如404或500fetch的Promise依然会正常解决resolve。你需要在.then()中通过检查Response.ok或Response.status属性来判断请求是否真正成功。这是fetch与许多旧式库如jQuery.ajax它会在HTTP错误时触发错误回调一个关键的行为差异也是新手最容易忽略的“坑”。3. 发起你的第一个Fetch请求GET与POST3.1 最基本的GET请求让我们从一个最简单的例子开始获取一个公开的API数据。// 示例从JSONPlaceholder获取一篇帖子 fetch(https://jsonplaceholder.typicode.com/posts/1) .then(response { // 第一步检查响应是否成功状态码在200-299之间 if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } // 第二步解析JSON格式的响应体 return response.json(); }) .then(data { // 第三步处理获取到的数据 console.log(Post title:, data.title); }) .catch(error { // 第四步捕获和处理错误网络错误或上面抛出的错误 console.error(There was a problem with the fetch operation:, error); });逐行解析与注意事项fetch(‘https://...’)发起一个GET请求到指定URL。由于我们没有传递第二个参数配置对象optionsfetch默认使用GET方法。第一个.then(response { ... })当服务器返回响应无论状态码是什么时这个回调函数被调用参数response就是Response对象。if (!response.ok) { throw new Error(...); }这是必须的检查。response.ok是一个布尔值当状态码在200-299范围内时为true。如果为false我们手动抛出一个错误这个错误会被最后的.catch()捕获。如果不做这个检查即使服务器返回404你的代码也会继续尝试解析响应体很可能导致后续错误。return response.json();我们假设服务器返回的是JSON格式的数据。.json()方法读取整个响应体并将其解析为JavaScript对象。它返回一个新的Promise。第二个.then(data { ... })当response.json()的Promise解决后我们拿到解析好的数据data并在这里进行业务处理如打印标题。.catch(error { ... })捕获整个Promise链中发生的任何错误。这包括网络错误如域名无法解析、连接被拒绝。我们在第一个.then()中手动抛出的HTTP错误。响应体解析错误例如服务器返回的不是合法JSON但.json()被调用了。实操心得错误处理是门面很多初学者会忘记检查response.ok。请务必养成习惯每次使用fetch第一个.then里先检查响应状态。一个健壮的错误处理机制是生产环境代码和玩具代码的重要区别。3.2 发送数据的POST请求向服务器提交数据例如创建新资源我们通常使用POST、PUT或PATCH方法。这需要通过fetch的第二个参数——配置对象options来指定。// 示例向JSONPlaceholder提交一篇新帖子 const newPost { title: My New Post, body: This is the content of my new post., userId: 1, }; fetch(https://jsonplaceholder.typicode.com/posts, { method: POST, // 指定请求方法 headers: { Content-Type: application/json, // 告诉服务器我们发送的是JSON // 可以添加其他头信息如授权令牌 // Authorization: Bearer your_token_here }, body: JSON.stringify(newPost), // 将JavaScript对象序列化为JSON字符串 }) .then(response { if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } return response.json(); }) .then(data { console.log(Post created successfully:, data); // 服务器通常会返回创建的资源包含新的ID console.log(New post ID:, data.id); }) .catch(error { console.error(Error creating post:, error); });配置对象options详解method: 字符串指定HTTP方法。常见的有‘GET’、‘POST’、‘PUT’、‘DELETE’、‘PATCH’。headers: 一个对象用于设置请求头。这是与服务器通信的关键部分。‘Content-Type’至关重要。它告诉服务器请求体的格式。发送JSON时必须设置为‘application/json’。发送表单数据时可能是‘application/x-www-form-urlencoded’或‘multipart/form-data’。其他常见头信息包括‘Authorization’用于身份验证、‘Accept’声明客户端希望接收的数据类型等。body: 请求体。对于GET或HEAD请求通常为null或undefined。对于POST、PUT等需要发送数据的请求这里放置要发送的数据。注意body的数据类型必须与headers中的‘Content-Type’匹配。发送JSON时需要使用JSON.stringify()将对象转为字符串。也可以发送FormData、URLSearchParams、Blob等类型。注意事项Content-Type的匹配如果body是JSON字符串但Content-Type没设置或设置错误如text/plain服务器可能无法正确解析你的数据导致请求失败。这是一个非常常见的问题源头。4. 深入Response对象与数据解析4.1 Response对象的常用属性和方法Response对象是你与服务器响应交互的入口。除了.json()它还有很多有用的属性和方法。fetch(https://api.example.com/data) .then(response { // 1. 状态信息 console.log(Status:, response.status); // 数字状态码如 200 console.log(Status Text:, response.statusText); // 状态文本如 OK console.log(OK?, response.ok); // 布尔值状态码2xx为true // 2. 头信息 console.log(Headers:, response.headers); // 获取特定的头信息 const contentType response.headers.get(content-type); console.log(Content-Type:, contentType); // 3. URL和重定向信息 console.log(URL:, response.url); // 响应的最终URL考虑重定向后 console.log(Redirected?, response.redirected); // 布尔值请求是否被重定向 // 4. 根据Content-Type选择解析方法 if (contentType contentType.includes(application/json)) { return response.json(); } else if (contentType contentType.includes(text/html)) { return response.text(); } else { // 其他类型如二进制文件 return response.blob(); } }) .then(parsedData { // 处理解析后的数据 console.log(Parsed data:, parsedData); });解析方法选择指南.json(): 将响应体解析为JavaScript对象。仅当服务器返回合法JSON时使用否则会抛出解析错误。.text(): 将响应体作为纯文本字符串返回。适用于HTML、XML、纯文本等。.blob(): 将响应体作为Blob二进制大对象返回。适用于图片、PDF、Excel等文件下载。.arrayBuffer(): 将响应体作为ArrayBuffer原始二进制数据缓冲区返回。用于更底层的二进制操作如音频处理。.formData(): 将响应体解析为FormData对象。适用于服务器返回multipart/form-data格式的情况较少见。4.2 处理非JSON响应和错误服务器并不总是返回JSON。你的代码需要足够健壮来处理不同的内容类型。fetch(https://api.example.com/some-endpoint) .then(async (response) { const contentType response.headers.get(content-type); if (!response.ok) { // 尝试获取错误信息可能是JSON也可能是文本 let errorBody; if (contentType contentType.includes(application/json)) { errorBody await response.json(); throw new Error(Request failed (${response.status}): ${JSON.stringify(errorBody)}); } else { errorBody await response.text(); throw new Error(Request failed (${response.status}): ${errorBody}); } } // 成功响应的处理 if (contentType contentType.includes(application/json)) { return response.json(); } else { return response.text(); } }) .then(data { console.log(Success:, data); }) .catch(error { console.error(Fetch error:, error.message); });这个例子展示了更完善的错误处理即使在HTTP错误时也尝试根据Content-Type去读取响应体以便获取服务器返回的更详细的错误信息。5. 实战进阶配置、超时与取消5.1 完整的Fetch配置选项fetch的第二个参数是一个强大的配置对象。除了method、headers、body还有其他常用选项const controller new AbortController(); const signal controller.signal; // 设置一个超时5秒后取消请求 setTimeout(() controller.abort(), 5000); fetch(https://api.example.com/slow-data, { method: GET, headers: { Content-Type: application/json, }, // 请求模式cors, no-cors, same-origin。默认cors用于跨域请求。 mode: cors, // 凭证如Cookie是否随请求发送omit, same-origin, include。默认same-origin。 credentials: same-origin, // 缓存模式default, no-cache, reload, force-cache, only-if-cached。 cache: default, // 重定向模式follow, error, manual。 redirect: follow, // 引用策略控制Referer头的发送。 referrerPolicy: no-referrer-when-downgrade, // 用于取消请求的AbortSignal对象 signal: signal, }) .then(response response.json()) .then(data console.log(data)) .catch(error { if (error.name AbortError) { console.log(Fetch request was aborted due to timeout.); } else { console.error(Fetch error:, error); } });关键配置解析mode: 处理跨域请求的关键。‘cors’默认允许跨域请求但需要服务器正确配置CORS头。‘no-cors’用于请求不会“伤害”服务器的资源如图片但返回的Response是“不透明的”你无法读取其内容。‘same-origin’只允许同源请求。credentials: 控制是否发送Cookie等凭证信息。如果需要携带Cookie进行身份验证必须设置为‘include’。在mode: ‘cors’下如果服务器允许凭证Access-Control-Allow-Credentials: true也必须设置此项。signal: 与AbortController配合用于取消请求。这是处理请求超时或用户主动取消的现代标准方式。5.2 实现请求超时与取消原生fetch没有直接的超时参数但我们可以利用AbortController和setTimeout轻松实现。/** * 一个带超时功能的fetch包装函数 * param {string} url - 请求URL * param {object} options - fetch配置选项 * param {number} timeoutMs - 超时时间毫秒 * returns {Promise} - 返回fetch的Promise超时则拒绝 */ function fetchWithTimeout(url, options {}, timeoutMs 10000) { const controller new AbortController(); const { signal } controller; // 设置超时定时器 const timeoutId setTimeout(() { controller.abort(); console.warn(Request to ${url} timed out after ${timeoutMs}ms); }, timeoutMs); // 发起fetch请求并传入signal const fetchPromise fetch(url, { ...options, signal, // 将AbortSignal合并到options中 }).finally(() { // 请求完成无论成功或失败清除定时器 clearTimeout(timeoutId); }); return fetchPromise; } // 使用示例 fetchWithTimeout(https://api.example.com/data, {}, 5000) // 5秒超时 .then(response { if (!response.ok) throw new Error(HTTP ${response.status}); return response.json(); }) .then(data console.log(Data received:, data)) .catch(error { if (error.name AbortError) { console.error(Request was aborted (likely due to timeout).); } else { console.error(Request failed:, error); } });这个fetchWithTimeout工具函数非常实用它封装了超时逻辑使得业务代码更加简洁清晰。注意在.finally()中清理定时器这是一个好习惯避免内存泄漏。6. 常见问题排查与性能优化技巧6.1 典型错误与解决方案速查表在实际开发中你会遇到各种各样的问题。下面是一个快速排查指南问题现象可能原因解决方案TypeError: Failed to fetch1. 网络连接问题离线、URL错误、域名不存在。2. 请求被浏览器扩展如广告拦截器或CORS策略阻止。3. 在fetch调用前发生了JS错误。1. 检查网络连接和URL拼写。2. 打开浏览器开发者工具的“网络(Network)”面板查看请求是否发出、状态是否为(blocked:other)或CORS error。3. 检查控制台是否有其他先于fetch的错误。SyntaxError: Unexpected token in JSON at position 0服务器返回的不是JSON可能是HTML错误页面但代码调用了.json()。1. 检查response.ok处理HTTP错误。2. 检查响应头Content-Type确认是application/json。3. 在调用.json()前先用.text()打印出响应体看看。请求成功但response.json()返回空或错误数据1. 服务器返回的JSON格式不正确如末尾有多余逗号。2. 字符编码问题。1. 使用.text()获取原始字符串用JSON.parse()尝试解析捕获更具体的错误。2. 确保服务器使用UTF-8编码。Cookie或Session没有随请求发送fetch默认不发送凭证credentials默认为‘same-origin’或‘omit’取决于浏览器和模式。在fetch的options中显式设置credentials: ‘include’。同时服务器CORS配置必须包含Access-Control-Allow-Credentials: true且Access-Control-Allow-Origin不能为通配符*。跨域(CORS)请求失败服务器未正确配置CORS响应头。1. 后端开发人员需要配置Access-Control-Allow-Origin允许的源、Access-Control-Allow-Methods允许的方法、Access-Control-Allow-Headers允许的请求头。2. 前端可尝试设置mode: ‘no-cors’但这样你将无法读取响应内容。请求无法取消或超时无效未正确使用AbortController或signal未传递给fetch。确保创建了AbortController实例并将其signal属性传递给fetch的options。调用controller.abort()来触发取消。6.2 性能优化与最佳实践复用Headers对象如果你需要多次发送相同头信息的请求可以创建一个Headers对象实例并复用这比每次都传递一个普通对象字面量更高效。const myHeaders new Headers(); myHeaders.append(Content-Type, application/json); myHeaders.append(Authorization, Bearer token123); fetch(url1, { method: GET, headers: myHeaders }); fetch(url2, { method: POST, headers: myHeaders, body: data });谨慎处理大响应体对于非常大的JSON文件如你提到的超过500MB直接使用response.json()可能会阻塞主线程并消耗大量内存。此时应考虑使用流式处理。response.body是一个ReadableStream。你可以使用response.body.getReader()创建一个读取器分块(chunk)读取和处理数据而不是一次性加载到内存中。这需要更复杂的代码但对于大文件至关重要。使用async/await语法糖它能让异步代码看起来更像同步代码提高可读性。上面的例子用async/await重写async function fetchData() { try { const response await fetch(https://api.example.com/data); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const data await response.json(); console.log(data); } catch (error) { console.error(Fetch failed:, error); } } fetchData();async/await是基于Promise的语法糖错误处理用try...catch包裹逻辑更清晰。考虑请求竞态问题在SPA单页应用中快速切换视图可能触发多个请求。如果后发请求先返回可能会覆盖先发请求的结果导致状态错乱。解决方案是使用AbortController在发起新请求时取消旧的相同请求或者使用标志位如请求ID来忽略过时的响应。掌握了这些基础你已经能够应对日常开发中90%以上的网络请求场景。fetch的威力远不止于此在后续的篇章中我们将探讨更高级的主题如请求/响应拦截、流式处理、与Service Worker的结合等让你真正成为网络请求的驾驭者。记住扎实的基础是通往精通的必经之路多动手实践多思考背后的原理你写出的代码会越来越稳健。