拓冰建站拓冰建站
首页 / 资讯中心 / 正文

XHR Level 2 核心特性解析:从异步请求到文件上传实战

1. 从“点击按钮不刷新”说起为什么我们需要XHR如果你在2005年之前做网页开发想从服务器拿点新数据更新页面唯一的办法可能就是刷新整个页面或者用个隐藏的iframe来偷偷加载。用户体验不存在的。直到一个叫XMLHttpRequest简称XHR的对象出现才彻底改变了游戏规则。它让网页能在后台悄悄地和服务器“聊天”拿到数据后只更新页面上需要变的那一小块地方。这就是我们常说的“Ajax”异步JavaScript和XML技术的核心。今天虽然有了更现代的fetch API但XHR依然是理解Web异步通信的基石无数老项目、特定库比如文件上传进度还在用它。更重要的是它的设计思想和遇到的“坑”能帮你更好地理解整个网络请求的生态。简单说XHR就是一个浏览器内置的“信使”对象。你用JavaScript告诉它“去这个网址用这种方式GET或POST带上这些话数据。” 然后它就在后台默默执行等拿到服务器的回信了再通知你。整个过程页面可以完全无感知用户操作不会被中断。从你给的热搜词也能看出来大家关心的核心无非就是怎么发请求GET/POST、请求过程中会遇到什么妖魔鬼怪各种error、以及怎么处理这些响应。而XHR Level 2则是这个“老信使”的一次重要升级解决了初代XHR很多让人头疼的毛病。接下来我们就抛开教科书式的定义从实际怎么用、为什么这么用以及怎么避开坑的角度把这个话题聊透。2. 初代XHR一个“别扭”但强大的信使别看现在fetch用起来好像更顺手XHR的API设计带着浓厚的时代烙印有点“别扭”但功能绝对强大。理解它的基本使用是读懂很多遗留代码和特定场景的钥匙。2.1 创建与配置给信使指明方向使用XHR的第一步永远是创建它。虽然现在有new XMLHttpRequest()的标准方式但为了兼容一些老古董浏览器比如IE6你可能会在旧代码里看到这种写法var xhr; if (window.XMLHttpRequest) { // 现代浏览器 xhr new XMLHttpRequest(); } else { // 老掉牙的IE5、IE6 xhr new ActiveXObject(Microsoft.XMLHTTP); }现在你基本可以忽略ActiveXObject直接使用new XMLHttpRequest()。创建好信使后你得告诉它任务详情这是通过.open()方法完成的。xhr.open(method, url, async);method: 字符串指定HTTP方法。从热词就能看出GET和POST是绝对的主角占了90%以上的场景。GET用于获取数据参数通常挂在URL后面像?namevaluePOST用于提交数据数据放在请求体里更安全能传的数据量也更大。这也是热词里“get和post的区别”被频繁搜索的原因。url: 字符串请求的目标地址。这里有一个大坑同源策略。浏览器默认禁止XHR向不同协议、域名、端口的地址发请求除非对方服务器明确允许通过CORS头。你搜到的很多net/http: request canceled、context deadline exceeded错误都可能源于跨域问题没处理好。async: 布尔值是否异步。这个参数99.9%的情况你都应该设为true。如果设为false请求会变成同步的在收到响应之前整个浏览器页面都会“卡住”用户体验极其糟糕。这个设计现在基本被废弃了。配置好任务后你还可以通过.setRequestHeader()方法给请求戴上“徽章”或“标签”告诉服务器一些额外信息。比如告诉服务器你发送的数据是JSON格式xhr.setRequestHeader(Content-Type, application/json);这里有个关键细节setRequestHeader必须在open()之后、send()之前调用否则可能会报错。2.2 发送与响应信使的旅程与回报一切准备就绪用.send()方法派信使出发。xhr.send(body);对于GET请求通常没有请求体body直接xhr.send(null)或xhr.send()。对于POST请求body就是你想要发送的数据。可以是字符串如JSON字符串、FormData对象常用于文件上传、URLSearchParams对象等。热词中multipart/form-data post、asp post数据就是POST请求不同数据格式的体现。信使出发后因为是异步的你的代码不会傻等。你需要监听信使的状态变化这就是onreadystatechange事件。xhr.onreadystatechange function() { if (xhr.readyState 4) { // 请求完成了 if (xhr.status 200 xhr.status 300) { // 请求成功HTTP状态码为2xx console.log(成功响应:, xhr.responseText); } else { // 请求失败HTTP状态码非2xx或网络错误 console.error(请求失败状态码:, xhr.status); } } };这里涉及到两个核心属性readyState: 表示请求/响应过程的当前状态从0未初始化到4完成。我们只关心4表示操作完成。status: HTTP响应状态码。200表示成功404找不到资源500服务器内部错误等等。判断成功时不要只写xhr.status 200而应该判断2xx的范围因为像201已创建、204无内容也都表示成功。响应数据在哪里responseText: 最常用以字符串形式返回响应体。如果服务器返回的是JSON你需要手动JSON.parse(xhr.responseText)。responseXML: 如果服务器返回的是XML文档且Content-Type正确这个属性会是一个可操作的XML DOM对象现在用得很少了。2.3 初代XHR的“痛”为什么需要Level 2用了一段时间初代XHR后开发者们发现了一些非常不爽的限制只能监听“完成”不能监听“进度”上传或下载一个大文件时用户完全不知道进度条该走到哪了体验很差。不能发送二进制数据想上传一张图片得先转成字符串比如Base64效率低且麻烦。跨域请求支持孱弱基本靠一些Hack手段如JSONP原生支持很弱。超时控制需要自己实现需要手动用setTimeout配合xhr.abort()来模拟不优雅。正是这些痛点催生了XMLHttpRequest Level 2。3. XHR Level 2给信使装上“增强套件”XHR Level 2并不是一个全新的对象它是对原有XMLHttpRequest接口的扩展。现代浏览器IE10及所有现代浏览器中的XMLHttpRequest对象默认就支持这些Level 2的新特性。这些特性让XHR变得更加强大和易用。3.1 进度事件让等待变得可知这是Level 2最实用的特性之一。你可以监听上传和下载的进度。// 监听下载进度 xhr.addEventListener(progress, function(event) { if (event.lengthComputable) { // event.loaded: 已加载的字节数 // event.total: 总共需要加载的字节数如果头部提供了Content-Length var percentComplete (event.loaded / event.total) * 100; console.log(下载进度 percentComplete %); } }); // 监听上传进度 xhr.upload.addEventListener(progress, function(event) { if (event.lengthComputable) { var percentComplete (event.loaded / event.total) * 100; console.log(上传进度 percentComplete %); } });注意xhr.upload是一个专门用于监听上传事件的XMLHttpRequestUpload对象。下载进度监听直接挂在xhr上上传进度监听则挂在xhr.upload上。服务器必须在响应头中正确设置Content-Lengthevent.lengthComputable才会为true否则你只知道加载了多少不知道总共有多少。3.2 原生超时与中止更好的流程控制Level 2提供了原生的超时属性取代了手动的setTimeoutHack。xhr.timeout 5000; // 设置超时时间为5秒 xhr.ontimeout function() { console.error(请求超时); };当请求时间超过timeout毫秒后请求会自动中止并触发ontimeout事件。同时你可以随时调用xhr.abort()来手动中止请求。3.3 响应类型responseType告别单一的字符串初代XHR主要用responseText和responseXML。Level 2引入了responseType属性让你在发送请求前就告诉XHR你期望的响应数据类型XHR会自动帮你转换好放在response属性里。xhr.responseType json; // 告诉浏览器我期待JSON xhr.onload function() { if (xhr.status 200) { // 直接使用xhr.response它已经是一个JavaScript对象了 var data xhr.response; console.log(data.name); } };responseType的常用值(默认): 字符串等同于使用responseText。text: 字符串。json: JavaScript对象。如果服务器返回的不是合法JSON解析会失败response为null但不会抛出错误到控制台需要你自己判断。blob:Blob对象非常适合处理二进制文件如图片、PDF。arraybuffer:ArrayBuffer对象用于处理原始的二进制数据。document: XMLDocument对象等同于responseXML。这个特性极大地简化了处理非文本数据如图片、音频的流程。3.4 FormData简化表单提交尤其是文件上传FormData对象是Level 2的另一个神器它能方便地构造表单数据并自动设置正确的Content-Type头multipart/form-data这对于文件上传是必须的。var formData new FormData(); formData.append(username, 张三); // 文本字段 formData.append(avatar, fileInputElement.files[0]); // 文件字段 xhr.open(POST, /upload); xhr.send(formData); // 无需手动设置 Content-Type 头结合上传进度监听你就能轻松实现一个带进度条的文件上传功能。3.5 增强的事件模型语义更清晰除了用onreadystatechangeLevel 2推荐使用更具体的事件onload: 请求成功完成时触发readyState为4且状态码为2xx。onerror: 请求发生网络错误时触发如DNS解析失败、网络断开。onabort: 请求被abort()方法取消时触发。onloadend: 请求结束时触发无论成功、失败还是中止类似于finally。使用这些事件可以使代码逻辑更清晰xhr.onload function() { console.log(请求成功完成:, xhr.response); }; xhr.onerror function() { console.error(网络请求出错); }; xhr.onabort function() { console.warn(请求被中止); };4. 实战案例构建一个带进度条的文件上传组件光说不练假把式。我们用一个完整的案例把Level 2的几个核心特性串起来用FormData处理数据用xhr.upload监听进度用responseType: json解析响应并用更清晰的事件处理。假设我们有一个简单的HTMLinput typefile idfileInput / button onclickuploadFile()上传/button div idprogressBar stylewidth: 300px; height: 20px; border: 1px solid #ccc; div idprogressFill styleheight: 100%; width: 0%; background-color: #4CAF50;/div /div div idresult/divJavaScript实现如下function uploadFile() { var fileInput document.getElementById(fileInput); var progressFill document.getElementById(progressFill); var resultDiv document.getElementById(result); var file fileInput.files[0]; if (!file) { alert(请先选择一个文件); return; } var xhr new XMLHttpRequest(); var formData new FormData(); formData.append(file, file); // 字段名‘file’需与后端约定 formData.append(comment, 这是一个测试文件); // 可以附加其他字段 // 1. 配置请求 xhr.open(POST, /api/upload); // 替换为你的上传接口 // 2. 设置响应类型为JSON方便后续处理 xhr.responseType json; // 3. 监听上传进度 xhr.upload.addEventListener(progress, function(event) { if (event.lengthComputable) { var percent Math.round((event.loaded / event.total) * 100); progressFill.style.width percent %; console.log(上传进度: percent %); } }); // 4. 使用更清晰的事件处理 xhr.onload function() { // 请求完成无论HTTP状态码是多少 if (xhr.status 200 xhr.status 300) { // HTTP状态码成功 var response xhr.response; // 已经是JSON对象 resultDiv.innerHTML p stylecolor:green;上传成功/pp文件地址${response.url}/p; console.log(服务器响应:, response); } else { // HTTP状态码失败如400 500 resultDiv.innerHTML p stylecolor:red;服务器错误状态码${xhr.status}/p; console.error(服务器返回错误状态码:, xhr.status, 响应:, xhr.response); } // 无论成功失败重置进度条可选 // setTimeout(() { progressFill.style.width 0%; }, 1000); }; xhr.onerror function() { // 网络层面错误如断网、跨域失败 resultDiv.innerHTML p stylecolor:red;网络请求失败请检查网络或接口地址/p; console.error(网络请求失败); progressFill.style.backgroundColor #f44336; // 变红色提示错误 }; xhr.ontimeout function() { // 请求超时 resultDiv.innerHTML p stylecolor:red;请求超时请重试/p; console.error(请求超时); }; // 5. 设置超时时间例如10秒 xhr.timeout 10000; // 6. 发送请求FormData会自动设置正确的请求头 xhr.send(formData); resultDiv.innerHTML p开始上传.../p; }这个案例里的几个关键点与避坑经验FormData的便捷性我们完全不需要手动设置Content-Type: multipart/form-data; boundary...这样复杂的请求头FormData和XHR配合会自动处理。这是处理文件上传的首选方案。进度监听的真实性event.lengthComputable依赖于服务器在响应OPTIONS预检请求或实际请求时对上传内容提供准确的Content-Length头部。如果后端处理不当这个值可能为false导致无法计算百分比。调试时务必检查网络面板。onload与onerror的分工onerror只在网络层面发生错误时触发如ERR_CONNECTION_REFUSED,ERR_CORS_FAILED。如果请求成功到达服务器但服务器返回了400 Bad Request或500 Internal Server Error这属于HTTP协议层面的“成功”请求-响应流程完成了所以会触发onload而不是onerror。判断业务成功与否必须在onload里检查xhr.status。responseType: json的陷阱设置了这个属性后如果服务器返回的不是合法JSON字符串xhr.response会是null且控制台不会报错。你可能会疑惑为什么数据是null。因此在onload里使用xhr.response前最好加一个非空判断。超时处理对于上传大文件超时时间xhr.timeout要设置得合理一些不能太短。超时触发的是ontimeout事件请求会被自动中止。5. 与Fetch API的对比我该用哪个既然有了更现代、基于Promise的fetch API为什么还要学XHR这是一个很实际的问题。XHR的优势即Fetch的不足进度监控fetchAPI目前截至2023年仍没有原生支持上传/下载进度监控。虽然可以通过Response.body的ReadableStream来模拟下载进度但非常繁琐。而XHR的progress事件是原生、简单且稳定的。对于文件上传等需要进度反馈的场景XHR仍是更好的选择。请求超时fetch本身不支持超时设置需要借助AbortController和setTimeout来实现不如XHR的xhr.timeout属性直接。更细粒度的控制与状态XHR可以获取readyState的各个中间状态虽然大多数时候用不到。fetch则更“高层”只关心最终结果。浏览器兼容性XHR的兼容性几乎覆盖了所有能跑JavaScript的浏览器。fetch在旧浏览器如IE中需要polyfill。Fetch API的优势更简洁的API基于Promise链式调用.then().catch()比XHR的事件回调或嵌套回调更清晰更容易与async/await结合。更合理的默认行为fetch不会像XHR那样遇到HTTP错误状态码404500就跑到onerror里。它只在网络故障时reject Promise对于HTTP错误它会resolve一个ok属性为false的Response对象让你统一处理。更现代的流式处理fetch的响应体Response.body是一个ReadableStream可以用于处理大型数据流更符合现代Web标准。与Service Worker等新技术集成更好。个人选择建议新项目且不需要上传进度优先使用fetch API代码更简洁现代。需要精确的上传/下载进度反馈使用XMLHttpRequest。维护老项目遵循项目原有技术栈。需要极致的浏览器兼容性不考虑IE两者都可以fetch需polyfill。如果必须兼容IEXHR更稳妥。6. 那些年我们踩过的XHR的“坑”结合你提供的海量错误热词很多都是开发者在实际使用XHR以及广义的网络请求时遇到的。我们来分析几个典型场景场景一跨域CORS错误错误关键词net/http: request canceled while waiting for connection,context deadline exceeded,Access-Control-Allow-Origin这是前端开发者的“必修课”。浏览器控制台看到CORS错误根本原因是你的前端页面地址如https://frontend.com向另一个域如https://api.backend.com发起了XHR请求而api.backend.com的响应头中没有包含Access-Control-Allow-Origin: https://frontend.com或*。解决方案后端解决推荐让后端API服务器在响应头中正确配置CORS。例如Node.js Expressapp.use((req, res, next) { res.header(Access-Control-Allow-Origin, https://frontend.com); // 或 *不推荐生产环境使用 res.header(Access-Control-Allow-Methods, GET, POST, PUT, DELETE); res.header(Access-Control-Allow-Headers, Content-Type, Authorization); if (req.method OPTIONS) { return res.sendStatus(200); // 对预检请求快速响应 } next(); });前端开发环境代理在本地开发时使用Webpack DevServer、Vite等的代理功能将API请求转发到同源的开发服务器再由开发服务器去请求真实后端绕过浏览器CORS限制。JSONP仅限GET请求一种古老的跨域Hack利用script标签没有跨域限制的特性。现在已基本被CORS取代。场景二超时与网络错误错误关键词client.timeout exceeded while awaiting headers,context deadline exceeded,net/http: request canceled这些错误通常指向网络问题或服务器响应太慢。xhr.timeout设置过短在慢网络环境下请求未在规定时间内完成。服务器处理时间过长后端接口性能有问题。不稳定的网络环境用户网络断开或波动。处理策略合理设置xhr.timeout对于重要操作可以设置长一些如30秒并给用户提示“请求可能较慢”。在ontimeout和onerror事件中提供友好的用户提示并提供重试按钮。实现指数退避等重试机制提升用户体验。场景三SSL/TLS证书问题错误关键词x509: certificate,certificate has expired这在访问自签名证书的HTTPS后端或证书过期的生产环境时会出现。浏览器出于安全考虑阻止了请求。开发环境可以让后端使用受信任的证书如Let‘s Encrypt或者前端在确保安全的情况下临时访问HTTP不推荐或让用户手动在浏览器中信任该证书对普通用户不现实。生产环境必须使用有效的、受信任的SSL证书。这是不可妥协的安全要求。场景四请求被取消Canceled错误关键词request canceled这通常不是代码错误而是预期行为你在请求完成前调用了xhr.abort()。用户快速导航离开了当前页面浏览器自动取消了未完成的请求。在单页应用SPA中组件卸载前未清理的请求可能被框架取消。应对在onabort事件中做必要的清理工作避免内存泄漏。对于因页面跳转导致的取消通常无需处理。理解这些错误背后的原因能让你在遇到控制台一片红的时候不再慌张而是能快速定位问题是在前端配置、网络环境还是后端服务。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门