ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Chrome DevTools编辑重发与流式打印:前端接口调试实战指南

Chrome DevTools编辑重发与流式打印:前端接口调试实战指南 1. 为什么编辑重发是比刷新页面高得多的调试方式1.1 从一次传参错误说起我刚入行那会儿最怕调接口。前端把参数报出去后端说哎你传的字段不对我第一反应就是在页面里改代码、刷新、再跑一遍。一个十秒能做完的验证硬生生被我拖成十分钟。后来我在 Chrome DevTools 的 Network 面板里发现了 Edit and Resend这个功能中文版叫编辑并重新发送。它可以让你在不刷新页面、不动代码的情况下把已经发过的网络请求整个拿过来改一遍再原样发出去。听起来很简单但用对了地方调试效率能翻几倍。这个功能够干什么简单说它会把当前选中的网络请求保存成一个可编辑的模板里面包含了 URL、请求方法、请求头、请求体你改完点发送浏览器就能立刻再发起一次请求同时新的请求会出现在 Network 面板里。你可以来回对比改动前后的响应差异定位到底是 Header 少了、参数错了还是服务端某块逻辑压根没兼容。这个功能适合谁前端、全栈、QA甚至后端写接口调试的人都适合。尤其是当你不需要完整跑一遍业务场景只想单独验证某个接口在特定输入下的表现时编辑重发比 UI 操作甚至比 Postman 都快。因为它直接继承了页面上真实的请求上下文不用你手动去复制 Cookie、Token、时间戳这些零碎字段。1.2 DevTools 里那杯冷掉的咖啡默认请求记录不是用来重发的很多人看 Network 面板扫一眼状态码、耗时、响应内容然后就走了。其实 Network 里每一行请求都是一个证据记录了页面加载过程中真实产生的所有网络交互。但默认情况下你刷新页面之后这些记录会被新的请求顶掉。所以我的建议是调试任何接口问题之前先把 Network 面板的 Preserve log 勾上。这个选项能保证页面跳转或刷新时之前的请求记录不被清空否则你会发现想重发的请求早就被刷没了。另一个常被忽略的是请求类型的过滤。如果页面用了 fetch并且你只想看 XHR/Fetch 类型的请求直接在 Network 面板上方点击 Fetch/XHR 过滤器就能把图片、CSS、JS 这些静态资源排除掉列表一下子清爽很多。编辑重发针对的就是这些真实请求而不是浏览器对静态文件的自动加载。如果你需要重发的请求是个跨域请求编辑重发这个功能本身不欺骗浏览器它还是会遵循 CORS 规则所以像第三方接口要求预检的浏览器照样先发 OPTIONS 再发真实请求。这一点不要误解为编辑重发可以绕过跨域它没那么神奇但正因为「真实」它才能还原线上环境的真实问题。2. Edit and Resend 的完整操作链路与隐藏细节2.1 入口在哪别只在右键菜单里找右键 Network 面板里的某一条请求记录会弹出上下文菜单里面能看到 Edit and Resend这是最直观的入口。但很多人不知道点开请求详情页后在 Headers 标签页的最上方同样有一个 Edit and Resend 按钮。当请求记录很多、右键不太好点的时候从详情页进入反而更稳。还有一个隐藏入口在请求列表上方的搜索框里直接输入 URL 关键字把目标请求筛出来之后右键操作。尤其是接口地址带动态参数时用 CtrlFWindows/Linux或 CmdFMac搜索比肉眼找靠谱得多。有一点要注意如果想编辑重发的请求是页面主文档加载发出的也就是类型为 document这个功能一般不起作用。它能重发的是 XHR、fetch、WebSocket 等资源请求和部分动态请求静态文档这类浏览器核心跳转请求不在编辑重发的常规业务内。点击 Edit and Resend 之后DevTools 会弹出一个对话框里面从上到下依次是请求 URL请求方法可以改成任意合法方法请求头列表每行一个 Header可以增删改请求体区域如果是 POST、PUT、PATCH 这类带 body 的请求这里会显示原始请求体可以直接编辑很多新手第一次看到这个界面会愣住因为里面的请求头特别多什么 Accept、User-Agent、Origin、Referer、Cookie 全都有。不用怕你改标题里关注的字段就好其余保持默认没影响。2.2 可编辑字段逐个过URL、Method、Headers、BodyURL 可以直接在文本框中改你可以换域名、换路径、改 query 参数。比如线上接口报错我想确认测试环境是否同样报错通常我先把 URL 从https://api.example.com/v1/order改成https://test-api.example.com/v1/order然后把 Host、Origin 这些头一并改掉再点发送。这里就有一个关键点改了 URL 的域名之后原来带在 Header 里的 Cookie 大概率会失效因为 Cookie 是按域隔离的。所以如果你新改的 URL 是另一个环境请务必检查 Header 里有没有带跟域名绑定的 Cookie、Authorization 之类的东西。Method 下拉框支持 GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS 等常见方法。当你把 POST 改成 GET 时底下的请求体并不会自动清空浏览器发出请求时GET 请求默认没有请求体就算你留着编辑器里的内容它也不会被发出去但为了规范建议手动清掉。反过来把 GET 改成 POST 时请求体区域会变成可编辑状态但默认是空的你需要自己粘参数。Header 区域的每一行左边是键右边是值。可以直接双击修改。想要新增 Header在末尾空白行直接输入想要删除把那一整行清空或者点击行尾的 × 就行。这里面需要特别留意的是 Content-Type它决定服务端怎么解析你的请求体。如果你请求体里放的是 JSON 字符串Content-Type 就应该是 application/json如果是表单格式就应该是 application/x-www-form-urlencoded 或 multipart/form-data。很多改了半天服务端读不到参数的问题就是 Content-Type 和请求体格式不匹配导致的。请求体区域则比较简单粗暴它是纯文本编辑器你直接把改好的 JSON 或字符串放进去就行。如果原始请求是 Form Data 表单格式请求体看起来可能是一串keyvaluekey2value2的结构也很好理解。但要注意DevTools 不会帮你做 JSON 格式校验也不会自动压缩或者转义你没写对服务端收到的就是你写的原样。2.3 重发后的流量去哪看New Request vs 响应对照编辑重发不会覆盖原始请求它会在 Network 面板里新生出一条请求记录名字通常带一个copy后缀或者你会在请求名称里看到类似order (copy)的标识Chrome 不同版本显示略有不同。这样一来原始请求和编辑后的请求会同时存在你可以点选它们分别查看响应也可以按住 Ctrl/Cmd 多选后对比响应差异。这个对比非常重要因为调试的最终目标不是发出去成功而是改动是否引起了预期的变化。想看两个请求的响应内容差异我通常左边选原始请求右边选新建的 copy 请求然后在 Response 标签页里肉眼扫。如果响应是 JSON我更推荐先把两个响应都复制到某个对比工具里或者用 DevTools 的 Network 面板里根本不提供差异对比靠肉眼扫 JSON 容易漏。所以我自己的习惯是重发前在请求体里故意加一个容易识别的标记参数比如debug_tag: change-1看返回结果里是否跟着变化这样就能一眼确认这次请求到底打到哪段逻辑了。重发请求之后还有一个细节经常被忽略重发的请求不会自动带上前一个请求的服务端响应数据比如某些 SPA 页面会把 Token 存在内存变量里第二次请求如果服务端校验 Token 是否和当前会话一致而你重发时拿的还是旧 Token就会得到 401。遇到这种情况不要怪编辑重发工具它只负责拷贝发起时的快照不负责维护你的业务状态。3. 编辑重发背后的原理浏览器到底帮你做了什么3.1 Chrome 是如何快照一个请求的要理解编辑重发为什么好用先得知道浏览器在请求发生的那一刻保存了什么。Chrome DevTools 的 Network 面板本质上是一个网络事件记录器当页面发出一个请求浏览器内核的网络栈会产生一系列事件DevTools 通过协议把这些事件的数据收集下来包括请求行Method URL HTTP 版本、请求头、请求体。编辑重发时DevTools 把这些快照数据填充到编辑器里你点击发送后它调用浏览器底层的能力重新发起一次网络请求。这个底层能力是 Chrome DevTools 协议里跟 Network 相关的接口。也就是说编辑重发并不是模拟一个请求而是通过浏览器自己发出去。这也是它跟 Postman、curl 最本质的区别——请求的发起者是浏览器本身所以浏览器会自动带上比如 CORS 策略判断、Cookie 规则、代理设置等等。带来的好处是更接近真实环境坏处是某些只能在服务端管控环境下复现的问题你没法绕过浏览器的限制来测试。但这也是为什么很多人反馈在 Postman 里请求没问题页面里就报错用编辑重发就能复现页面里的报错。因为编辑重发保留了页面请求的完整上下文包括 Origin、Referer、User-Agent 这些浏览器特有的头以及当前登录态下的 Cookie这些上下文拼接起来服务端看到的就和页面真实发出的一模一样。3.2 为什么改一处 Header 经常需要连带改另一处HTTP 请求不是单打独斗头与头之间常常有依赖关系。最常见的组合是 Content-Type 和请求体格式其次是 Authorization 和 Cookie 之间的互斥或互补再就是 Origin 与 CORS 的关系。用编辑重发时如果你只改了 URL 的域名没有改 Origin浏览器发起请求后服务端进行 CORS 检查时发现 Origin 指向的还是旧域名要么拒绝要么返回的响应没有被浏览器放行让你看到数据结果表现为请求 200 但代码拿不到响应。另一个典型是 Host 头。HTTP/1.1 开始请求必须带 Host 头表示你要访问的主机名。Chrome 的编辑重发界面里请求头列表中一般包含 Host 字段。你改了 URL 的域名Host 往往不会自动同步更新。如果你的网络环境里存在反向代理或虚拟主机服务端按照 Host 来分发请求Host 不对就会 404。所以修改 URL 之后检查一下 Host 是否跟新域名一致该改就改。还有 Cookie。页面请求通常会自动带上 Cookie但 DevTools 会在请求头列表里展示 Cookie 头的值。你换环境之后这个 Cookie 可能是旧环境的如果新环境不认就会出现会话失效。最佳实践是如果需要带登录态重发先在新环境里正常操作一遍页面确认登录成功然后从新的请求快照里找到正确的 Cookie再粘到编辑重发对话框里。3.3 重发次数有限制别被现象骗了有人发现同一个请求连续编辑重发十几次之后DevTools 好像卡顿了或者重发按钮没反应。其实浏览器本身没有次数限制但 DevTools 界面会累积大量请求记录。如果你之前勾选了 Preserve logNetwork 面板里的记录会越来越多渲染压力增大这时候界面会变得很卡。你可以点击面板左上角的 Clear 按钮禁止符号图标清空记录再重新触发需要重发的请求。不需要担心清空记录会影响别的功能这只是清空列表不影响页面状态。另一个误导现象是重发一个已经 404 的请求新的请求也 404于是觉得重发没用。别急你得看清楚404 是服务端返回的状态说明请求成功送到了服务端服务端找不到资源。问题不在重发这个动作而在于你的修改没有命中正确的路径。这种时候回到 URL 和路由配置去排查而不是继续按发送按钮。4. 局限性与更猛的调试姿势4.1 不适合的场景登录态过期、动态签名、文件上传编辑重发虽然好用但并不是万能的。网络上常见的演进场景它处理不了登录态过期如果 Token 有效期只有几分钟你从上一个请求里拿到的 Authorization 头早就失效了重发只能拿 401。需要先在页面上刷新登录态再重新抓取一个新鲜请求。动态签名有些业务会在请求发起前用 JS 对参数做签名比如计算sign md5(params secret)。编辑重发时你只能改 paramssign 是旧的服务端验签不通过请求直接被拒。这种场景必须用脚本或者借助抓包代理工具去改请求逻辑。文件上传multipart/form-data 类型的请求体通常包含随机生成的 boundary而且文件内容太长DevTools 在请求体里展示的是序列化后的字符串手动编辑容易把文件内容搞坏。稳妥做法是通过页面重新拼装一次上传流程再抓新的请求快照。WebSocket 消息编辑重发主要用于 HTTP/HTTPS 请求WebSocket 帧的编辑需要到 Messages 面板那里没有类似的编辑重发功能。如果你发现上面三类场景占了你工作的 80%那建议你换用专门地网络调试工具比如代理抓包软件或者允许自定义脚本的 HTTP 客户端。浏览器自带的编辑重发定位就是快速轻量别把它当全能接口测试平台。4.2 copy as fetch 与 curl 的适用边界编辑重发之外Chrome 还提供了 Copy as fetch 和 Copy as cURL。右键请求记录就能看到这些选项。Copy as fetch 会把当前请求转换成一段 fetch 代码包含完整的 URL、方法、Headers、BodyCopy as cURL 则转换成 curl 命令。什么时候用 Copy as fetch 而不是编辑重发当你想在页面上下文中反复改动并集成到自动化脚本里的时候。比如你要写一个循环请求十次的脚本每次改一个参数那就不能手动点十次重发而是复制成 fetch 代码塞进 DevTools 的 Console 里用 for 循环去跑。这样你能利用 Console 的编程能力比如动态生成参数、统计耗时、把结果存到全局变量里。Copy as cURL 则适合在没有浏览器的环境里复现问题比如你是后端需要把请求带到自己的服务排查用终端执行 curl 命令就能完成。注意 cURL 和 fetch 生成的代码里包含敏感信息Cookie、Token分享出去之前记得脱敏。4.3 批量构造请求用一段脚本接管当你已经能构造出正确的 fetch 请求后批量重发就变得非常简单。我经常在 Console 里这样写const baseOptions { method: POST, headers: { Content-Type: application/json, Authorization: Bearer localStorage.getItem(token) } }; async function batchSend(items) { for (let i 0; i items.length; i) { const url https://api.example.com/order; const res await fetch(url, { ...baseOptions, body: JSON.stringify({ ...defaultOrder, ...items[i], traceId: batch- i }) }); const data await res.json(); console.log(第 i 次:, res.status, data); } } batchSend([{ skuId: sku_1, count: 1 }, { skuId: sku_2, count: 3 }]);这段代码的好处在于它跑在浏览器的页面上下文里天然带着当前页面的 Cookie 和登录态同时又能自由控制循环变量批量验证不同参数。如果你想把它封装成一个更优雅的函数以后随时在 Console 里调用我建议把 URL、默认参数、Token 读取逻辑全部参数化方便复制到项目代码里作为调试工具函数。5. fetch API 中 ReadableStream 打印不出来的问题5.1 背景fetch 返回的不是数据而是数据流很多前端朋友在开发时写了几行const res await fetch(/api/data); console.log(res.body);然后控制台显示ReadableStream { locked: false, state: readable, supportsBYOB: false }以为拿到了数据没有。因为 fetch API 的 Response 对象其 body 属性是一个 ReadableStream它是响应体的底层数据流。也就是说网络数据是一块一块地到达的不是一次性打包成一个字符串给你。只有当你把流读完才能得到完整的数据。类似地你调用res.text()、res.json()时浏览器内部做的就是读流 解析两步。我见过不少人把console.log(res.body)打出来的 ReadableStream 当成 bug觉得接口没返回数据实际上接口可能已经返回了只是你还没消费这个流。ReadableStream 这个设计是为了支持大文件下载、流式渲染、服务器推送等场景。如果浏览器把整个响应先缓存完再返回那下载大文件时内存就爆了。5.2 打印 ReadableStream 的三种基本姿势姿势一用 res.text() 转成字符串最简单粗暴的方式。如果你确定的响应体不大用res.text()一次读完const res await fetch(/api/data); const text await res.text(); console.log(text);这样你就能看到完整的响应字符串。如果接口返回的是 JSON你可以JSON.parse(text)或者直接用res.json()不过那样你就拿不到纯字符串了。需要注意res.text()一旦调用body 流就被读完了再调用res.json()就会报错。同一份响应只能读一次。姿势二用 res.json() 直接打印对象这是最常用的调试方式适合 JSON 接口const res await fetch(/api/data); const json await res.json(); console.log(json);如果格式正确Console 里会显示可展开的对象树。我强制自己注意的一点是json()内部对非法 JSON 会抛异常异常信息有时候很含糊。遇到Unexpected end of JSON input时请先改成res.text()看看返回了什么可能是空白、HTML 错误页、或者富文本提示。姿势三用流式读取一次性打完整如果你希望既保留流式读取的好处又能一次性看到结果可以手动读流拼接const res await fetch(/api/data); const reader res.body.getReader(); const chunks []; while (true) { const { done, value } await reader.read(); if (done) break; chunks.push(value); } const full new TextDecoder().decode(new Blob(chunks)); console.log(full);这个做法相当于自己实现了一个 text()好处是你可以拿到每个 chunk 的字节方便分析传输进度。正常情况下我推荐项目里封装一个readAll(res)工具函数调试期用不要每次手写这么长。5.3 流式打印一个字节一个字节看响应当你遇到接口响应很大或者服务端是逐块返回的比如 SSE、流式补全你就需要实时看到数据流的内容而不是等所有数据到齐。此时用getReader()配合read()循环每读到一块就 console.log 一次。const res await fetch(/api/stream); const reader res.body.getReader(); const decoder new TextDecoder(); while (true) { const { done, value } await reader.read(); if (done) { console.log(流读取完毕); break; } const chunk decoder.decode(value, { stream: true }); console.log(新增数据块:, chunk); }这段代码里有个关键点decoder.decode(value, { stream: true })用于解决多字节字符在分包时被拆散的问题。比如一个中文字符的 UTF-8 编码占三个字节第一次 read() 可能只拿到前两个字节直接解码会得到乱码。使用stream: true后解码器会缓存不完整的字节序列等待下一个 chunk 到来时再补齐这样打印出来的文本就是连续的、可读的。另外read()返回的 value 类型是 Uint8Array所以不能直接当字符串拼接。你可以通过new TextDecoder()转换成字符串或者用String.fromCharCode(...value)但后者在遇到大块二进制数据时容易爆栈不推荐。用流式打印最大的价值是你可以直观地看到服务端到底多久推一块数据。如果每个 chunk 间隔很大说明服务端逻辑有吞吐瓶颈如果一开始就推了一大块后面全是小块可能是首包优化的问题。这些在做接口耗时排查时特别有用。5.4 打印过程中把流用坏了怎么办克隆分支前面我提到Response 对象的 body 流只能被读取一次。如果你先console.log(res.body)打印出一个 ReadableStream然后又去调res.json()会报错Body is unusable。这是因为 Body 接口内部有一个bodyUsed标志位一旦读取启动流就被锁定后续操作直接拒绝。这是设计上的保护机制防止同一份数据被不同解析器各自消费。那如果我既要打印流内容又要用接口数据渲染页面怎么办两种方案先 clone() 再读如果 Response 还没被消费调用res.clone()得到一个副本然后在副本上做流式打印原 response 继续做业务解析。注意 clone() 必须在使用 body 之前调用一旦原 response 已经开始读流clone() 会失败。const res await fetch(/api/data); const resForDebug res.clone(); const reader resForDebug.body.getReader(); // 喂这里别去读 res只管读 resForDebug const text await res.text(); console.log(业务数据:, text);用响应缓存到变量如果你只是调试一下完全可以先const raw await res.text(); console.log(raw); const json JSON.parse(raw);一次性消费完再把解析结果投入业务。这个方案最简单不涉及 clone 的边界问题。实际开发中我更推荐第二种因为 clone() 在流式场景下会在内存里复制一份数据如果大文件下载双倍内存是吃不消的。Clone 适合小接口调试大流还是得规划好一次消费路径。6. 实战组合编辑重发 流式打印排查接口慢/接口报错6.1 场景一接口超时看流里读到哪个 chunk 卡住有一次同事反馈某个下载接口偶尔挂起页面一直转圈。我们先用 Network 面板抓请求看到耗时 30 秒都没有结束。这时候用编辑重发显然不够因为编辑重发只能看到最终响应但如果接口永远不返回你还是看不到数据。于是我们直接在 Console 里用 fetch 手动发起同一个请求然后流式打印const controller new AbortController(); const timer setTimeout(() controller.abort(), 30000); try { const res await fetch(/download/export, { signal: controller.signal }); console.log(收到响应头:, res.status, res.headers.get(content-type)); const reader res.body.getReader(); const decoder new TextDecoder(); let total 0; while (true) { const { done, value } await reader.read(); if (done) break; total value.length; console.log(读取到字节数:, total, 最近chunk:, decoder.decode(value)); } } catch (e) { console.error(请求中断:, e); }日志很快就显示响应头一到达就开始读 stream 了但读了几百字节之后后面 25 秒再没轮到任何 chunk。说明问题不在浏览器没发出去而在服务端生成了响应头后迟迟没有把后续数据写完或者中间网络层把连接挂住了。配合服务端日志一查发现是服务端在循环读取数据库时超时了。这个案例里如果你只依赖编辑重发完全看不到卡在哪一步因为编辑重发不会告诉你响应头已收到但 body 迟迟未传完。6.2 场景二响应是流式 JSON 无法用 console.table另一个真实场景是某个 AI 接口会以分块方式返回多段 JSON每段都含部分结果。如果只用res.json()因为流内容不是标准完整 JSON你会直接收到解析错误。这时需要自己按行解析。我们的做法是先用流式打印把原始 chunk 内容打出来再根据分隔符截断。const res await fetch(/api/chat/message); const reader res.body.getReader(); const decoder new TextDecoder(); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); // 假设服务端每段数据以 \n 结尾 const lines buffer.split(\n); buffer lines.pop(); // 保留下半段不完整内容 for (const line of lines) { if (!line.trim()) continue; try { const data JSON.parse(line); console.log(解析成功:, data); } catch (e) { console.log(临时片段:, line); } } }这里面我把buffer lines.pop()写在循环内是为了把可能仍缺少后续字节的最后一个元素留给下一轮拼接。等循环结束后如果 buffer 里还有残留再尝试解析一次。这个模式是我目前为止见过最稳妥的流式 JSON 调试方案。6.3 结合 Performance 面板与断言日志的三段式排查单凭流式打印和编辑重发还是不够系统。我自己的排查链路分三步第一步用编辑重发把请求完整复制出来去掉动态签名和不可控 Headers发到测试环境确认是不是请求本身有问题如果测试环境正常那大概率是数据差异或环境配置差异。第二步用 Copy as fetch 生成脚本在请求各阶段埋日志。比如在发送前打印参数、收到响应头后打印状态、读取流时打印每个 chunk 的时间戳。为了减小噪音我通常只保留 chunk 长度和间隔毫秒数不打印 chunk 内容。let lastTs performance.now(); while (true) { const readerRes await reader.read(); const now performance.now(); if (readerRes.done) break; console.log(chunk 到达距上次, (now - lastTs).toFixed(1) ms, 长度, readerRes.value.length); lastTs now; }第三步结合 Performance 面板找到该请求在 Timing 里各阶段耗时。如果等待服务器响应时间Time to first byte很长说明服务端处理慢如果第一个字节很快但后面一直有数据传输则要考虑带宽或大对象处理。这段链路走完至少能定位问题的大头在哪一层再决定到底是前端需求改造还是后端优化。7. 我的习惯与最终经验补充我用编辑重发已经有五年多了积累了一些小习惯拿出来分享给大家。第一凡是碰到线上偶发接口问题第一件事不是看代码而是先打开 DevTools找到那条请求马上点击 Edit and Resend先确认它是不是稳定的可复现。如果能复现再把改好的参数复制出来贴到项目的 issue 或文档里方便别人复现。很多 bug 其实在第一次抓包时就已经漏出苗头只是大家忙着改代码没注意。第二编辑重发对话框里的 Header 列表我习惯先把 Cookie 和 Authorization 单独盯住。因为接口调试最烦人就是身份认证问题。如果你发现重发一次 401再试一次又能通十有八九是 Token 过期或单次有效。这种问题的确不适合用编辑重发多次发送需要写一个小脚本去刷新 Token 后重发。第三流式打印不要只盯着 Console。如果你的接口返回内容很长Console 里打印一大片照样看不过来。我建议在打印时给每个 chunk 加上序号和时间戳let idx 0; // 每次读到数据 console.log([${idx}], new Date().toISOString(), chunk);等排查完了把这些日志收集起来可以按时间线重放看整个流式传输是否规律。由于有肌肉记忆现在写流式调试代码我基本不打草稿直接手写 getReader 加 TextDecoder 就能跑。大家刚开始可能觉得繁琐多写几次就熟练了。第四也是最重要的一点编辑重发和流式打印本质上都是观察工具它们不能代替你理解 HTTP 和网络协议。你会发现靠谱的前端不会等接口出问题才打开 DevTools而是在每次开发时就有意识地观察请求结构、响应时机。调试能力就是靠这样日积月累起来的。这几招用熟之后再遇到奇怪的网络问题至少不会慌了。希望这篇文章能帮你在日常开发里少走点弯路。以后看到 Console 里打印出 ReadableStream别再以为接口挂了看到 Network 面板里某条请求不顺眼试着右键点一下 Edit and Resend也许问题当场就水落石出。
返回列表