十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Ajax异步请求实战:编码格式、参数赋值与泛微ecology9接口对接

Ajax异步请求实战:编码格式、参数赋值与泛微ecology9接口对接 1. 从一个老项目说起为什么今天还要聊 Ajax第一次接触 Ajax 是在一个后台管理系统里需求很简单点一下按钮表格里的数据要刷新但整个页面不能跳转、不能白屏。当时我的第一反应是用表单提交提交完再重定向回来结果用户体验极差滚动位置丢了、筛选条件也没了。后来换成 Ajax只把表格那一块重新渲染其他区域纹丝不动用户几乎感觉不到“刷新”这件事发生过。这就是 Ajax 最朴素的价值在不重新加载整个页面的前提下和服务器交换数据并局部更新界面。它的全称是 Asynchronous JavaScript and XML虽然名字里带 XML但现在绝大多数场景传的都是 JSON。异步数据传输是它的灵魂——请求发出去之后浏览器不会傻等用户可以继续操作页面等数据回来了再通过回调或者 Promise 处理。这篇文章适合谁看如果你正在维护一个前后端不分离的老系统或者你刚入门前端想搞明白“为什么我 fetch 一个接口页面就变了”又或者你在对接像泛微 ecology9 这类平台的 ajax 调用后端接口那这篇内容应该能帮到你。我会从整体设计思路讲到具体参数怎么设、编码格式怎么定、参数怎么赋值再把我踩过的坑整理成一张速查表。全程按一个真实项目推进的节奏来写不堆概念只讲能直接抄作业的东西。2. 整体设计思路Ajax 到底解决了什么问题2.1 同步请求的痛点和异步的取舍要理解 Ajax得先理解它替代了什么。传统的同步请求浏览器发出请求后会阻塞当前页面的交互直到服务器返回完整响应然后整页重绘。这个过程里用户看到的是白屏或者闪烁网络稍差一点就是漫长的等待。更麻烦的是页面状态全部丢失——你填了一半的表单、滚动到的位置、展开的菜单统统回到初始状态。异步请求把“等待”这件事从用户身上转移到了代码里。请求发出后立即返回主线程继续处理其他事情数据回来后再触发回调。代价是代码复杂度上升你得处理“数据还没回来时界面显示什么”“请求失败了怎么办”“多个请求的先后顺序”这些问题。所以异步不是免费的午餐它用代码的复杂度换来了体验的流畅度。我个人的取舍原则是凡是会改变页面主要内容的操作优先用异步凡是涉及页面整体跳转、权限校验、文件下载这类天然需要导航的行为老老实实用同步。硬把下载也做成 Ajax反而要处理 Blob、内存占用、下载进度这些额外问题得不偿失。2.2 数据格式的选型为什么 JSON 干掉了 XMLAjax 诞生时 XML 是主流因为它是标准化的、可校验的、跨语言的。但 XML 有个致命问题冗余。一个简单的用户对象XML 要写成username张三/nameage18/age/user而 JSON 只需要{name:张三,age:18}。在移动网络和大量接口调用的场景下这点体积差异会被放大。更重要的是JSON 和 JavaScript 的对象字面量几乎无缝衔接。JSON.parse()一行就能把字符串变成对象而解析 XML 得用 DOM 遍历写起来又臭又长。所以现在除了少数遗留系统和特定行业标准比如某些政务、金融接口基本都用 JSON。不过要注意JSON 不是万能的。它不支持注释、不支持二进制、日期类型也得转成字符串。所以涉及文件上传下载还是得走 FormData 或者二进制流不能硬塞进 JSON。2.3 请求方式的语义化选择GET 和 POST 是最常用的两个方法但很多人用得很随意。我的习惯是严格按语义来GET获取数据参数放在 URL 上幂等多次调用结果一样可以被缓存、被收藏、被分享。POST提交数据、创建资源参数放在请求体里非幂等每次调用可能产生不同结果。PUT/PATCH更新资源PUT 是全量替换PATCH 是局部更新。DELETE删除资源。为什么强调这个因为很多后端框架、网关、缓存层都依赖请求方法做路由和缓存决策。你把一个查询接口写成 POST缓存就失效了CDN 也帮不上忙。反过来把创建订单写成 GET浏览器预取或者爬虫一抓可能就多出一堆脏数据。提示GET 请求的 URL 长度在部分浏览器和服务器上有上限常见是 2KB 到 8KB参数多或者内容长的时候必须换 POST。3. 核心细节解析编码格式、参数赋值与请求构造3.1 ajax 请求设置编码格式别让中文变成乱码编码格式这个问题平时不遇到一遇到就是满屏的%E5%BC%A0%E4%B8%89或者问号。核心就两个地方要管请求头里的 Content-Type和字符集声明。最常见的三种 Content-TypeContent-Type适用场景参数位置编码说明application/x-www-form-urlencoded传统表单提交请求体keyvaluekey2value2默认按 UTF-8特殊字符需 encodeURIComponentapplication/json现代接口请求体JSON 字符串必须显式声明charsetutf-8multipart/form-data文件上传请求体分段由 boundary 分隔不手动设 charset用原生 XMLHttpRequest 时如果发 JSON一定要这样写const xhr new XMLHttpRequest(); xhr.open(POST, /api/user, true); xhr.setRequestHeader(Content-Type, application/json;charsetutf-8); xhr.send(JSON.stringify({ name: 张三, age: 18 }));注意charsetutf-8不能省。有些服务器默认按 ISO-8859-1 解析请求体中文就会乱码。用 fetch 的话Content-Type设成application/json通常就够了因为 fetch 默认按 UTF-8 编码但为了兼容老网关我还是习惯带上 charset。如果是 GET 请求参数拼在 URL 上中文必须用encodeURIComponent处理const keyword encodeURIComponent(异步数据传输); fetch(/api/search?q${keyword});encodeURIComponent和encodeURI的区别也要记牢前者会把、、?这些分隔符也编码适合编码单个参数值后者保留这些符号适合编码整个 URL。用错了参数就会被截断。3.2 给 ajax 请求参数赋值几种常见姿势和坑参数赋值看起来简单其实花样不少。我按从简单到复杂列一下。第一种直接拼字符串。适合参数少、固定的场景。const id 1001; const url /api/detail?id id typebook;坑在于如果id来自用户输入可能包含特殊字符必须编码。而且参数一多字符串拼接可读性极差。第二种用 URLSearchParams。现代浏览器都支持自动处理编码。const params new URLSearchParams(); params.append(id, 1001); params.append(keyword, 异步数据传输); fetch(/api/search? params.toString());这个方式我最推荐干净、安全、不用手动 encode。第三种对象转查询串。自己写个工具函数或者用现成库。function toQuery(obj) { return Object.keys(obj) .map(k ${encodeURIComponent(k)}${encodeURIComponent(obj[k])}) .join(); }第四种POST 请求体赋值。发 JSON 就JSON.stringify发表单就URLSearchParams。// JSON 方式 fetch(/api/user, { method: POST, headers: { Content-Type: application/json;charsetutf-8 }, body: JSON.stringify({ name: 张三, age: 18 }) }); // 表单方式 const form new URLSearchParams(); form.append(name, 张三); form.append(age, 18); fetch(/api/user, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded;charsetutf-8 }, body: form });注意用URLSearchParams作为 body 时fetch 会自动把 Content-Type 设成application/x-www-form-urlencoded;charsetUTF-8一般不用手动改。但如果你手动设了别的就会覆盖掉。3.3 深入浅出一次请求的完整生命周期把一次 Ajax 请求拆开看大概经历这几个阶段创建请求对象new XMLHttpRequest()或者直接调fetch()。配置请求设置方法、URL、是否异步、请求头。发送请求send()把请求体发出去。等待响应这期间浏览器在后台和服务器通信主线程不阻塞。接收响应状态码、响应头、响应体陆续到达。处理数据解析 JSON、更新 DOM、处理错误。用 XMLHttpRequest 时第 4 到第 6 步靠onreadystatechange或者onload监听。readyState有 5 个值0 未初始化、1 已打开、2 已发送、3 接收中、4 完成。只有readyState 4且status在 200 到 299 之间才算成功。用 fetch 时它返回一个 Promise但有个大坑fetch 只在网络错误时 rejectHTTP 状态码 404、500 都不会 reject。所以必须手动检查response.okfetch(/api/user) .then(res { if (!res.ok) throw new Error(HTTP res.status); return res.json(); }) .then(data console.log(data)) .catch(err console.error(err));这个坑我见过太多人踩接口返回 500前端却以为成功了拿着错误页面去res.json()结果报解析错误排查半天。4. 实操过程从零搭一个可复用的请求封装4.1 原生 XMLHttpRequest 的完整写法虽然现在都用 fetch 或者 axios但理解原生写法对排查问题很有帮助。下面是一个带超时、错误处理、编码设置的完整例子function ajax(options) { const xhr new XMLHttpRequest(); const method (options.method || GET).toUpperCase(); const timeout options.timeout || 10000; // 处理 GET 参数 let url options.url; if (method GET options.data) { const params new URLSearchParams(options.data); url (url.includes(?) ? : ?) params.toString(); } xhr.open(method, url, true); xhr.timeout timeout; // 设置请求头 if (options.headers) { Object.keys(options.headers).forEach(key { xhr.setRequestHeader(key, options.headers[key]); }); } // 默认 JSON 编码 if (method ! GET options.data !options.headers) { xhr.setRequestHeader(Content-Type, application/json;charsetutf-8); } xhr.onload function () { if (xhr.status 200 xhr.status 300) { let result xhr.responseText; try { result JSON.parse(result); } catch (e) { // 不是 JSON 就原样返回 } options.success options.success(result); } else { options.error options.error(xhr.status, xhr.statusText); } }; xhr.onerror function () { options.error options.error(0, 网络错误); }; xhr.ontimeout function () { options.error options.error(0, 请求超时); }; // 发送请求体 let body null; if (method ! GET options.data) { body options.headers options.headers[Content-Type] application/x-www-form-urlencoded ? new URLSearchParams(options.data).toString() : JSON.stringify(options.data); } xhr.send(body); }调用方式ajax({ url: /api/user/list, method: GET, data: { page: 1, size: 20, keyword: 异步 }, success: res console.log(res), error: (code, msg) console.error(code, msg) });这个封装里我特意处理了 GET 参数编码、超时、非 JSON 响应这几个容易出问题的地方。实际项目里可以直接用也可以作为理解 axios 内部原理的参考。4.2 用 fetch 封装一个更现代的版本fetch 的 API 更简洁但需要自己补上超时、错误处理、编码这些能力。下面是我在项目里常用的一个封装async function request(url, options {}) { const { method GET, data, headers {}, timeout 10000 } options; const controller new AbortController(); const timer setTimeout(() controller.abort(), timeout); let finalUrl url; const finalHeaders { ...headers }; if (method.toUpperCase() GET data) { const params new URLSearchParams(data); finalUrl (url.includes(?) ? : ?) params.toString(); } let body null; if (method.toUpperCase() ! GET data) { if (data instanceof FormData) { body data; // FormData 不设 Content-Type浏览器自动带 boundary } else { finalHeaders[Content-Type] finalHeaders[Content-Type] || application/json;charsetutf-8; body JSON.stringify(data); } } try { const res await fetch(finalUrl, { method, headers: finalHeaders, body, signal: controller.signal }); clearTimeout(timer); if (!res.ok) { throw new Error(HTTP ${res.status} ${res.statusText}); } const contentType res.headers.get(content-type) || ; if (contentType.includes(application/json)) { return await res.json(); } return await res.text(); } catch (err) { clearTimeout(timer); if (err.name AbortError) { throw new Error(请求超时); } throw err; } }这个版本用AbortController实现超时比 XMLHttpRequest 的 timeout 更灵活因为可以中途取消。FormData的判断也很关键——上传文件时如果手动设了Content-Typeboundary 就会丢后端解析直接失败。4.3 对接泛微 ecology9 的 ajax 调用后端接口泛微 ecology9 这类 OA 平台前端页面通常跑在自己的框架里但底层还是标准的 HTTP 请求。对接它的后端接口时有几个点要特别注意第一鉴权。ecology9 的接口一般需要携带会话信息可能是 Cookie也可能是特定的 token 头。用 fetch 时记得带上credentials: include否则跨域情况下 Cookie 不会自动发送。fetch(/api/ecology/workflow/submit, { method: POST, credentials: include, headers: { Content-Type: application/json;charsetutf-8 }, body: JSON.stringify({ requestId: 12345, action: submit }) });第二参数格式。有些 ecology9 接口要求参数是 form 表单格式不是 JSON。这时候就得用URLSearchParams并且 Content-Type 设成application/x-www-form-urlencoded。我遇到过接口文档写的是 JSON实际却只认表单格式的情况排查了半天最后抓包才看出来。第三返回结构。ecology9 的接口返回经常包一层比如{ code: 200, data: {...}, msg: success }。前端不能只看 HTTP 状态码还得看业务 code。所以封装里最好加一层业务错误判断const result await request(/api/ecology/xxx, { method: POST, data: params }); if (result.code ! 200) { throw new Error(result.msg || 业务处理失败); }第四跨域和代理。开发环境里前端跑在 localhost后端在另一个域名直接请求会被浏览器拦截。常见做法是配一个开发代理把/api开头的请求转发到后端。这个在 webpack、vite 里都有现成配置不展开但要知道这是开发阶段的标准操作不是接口本身有问题。5. 常见问题与排查技巧实录5.1 中文乱码问题速查中文乱码是 Ajax 里最高频的问题没有之一。我整理了一张排查表现象可能原因排查方法解决方式请求参数中文变问号未编码或编码方式不对看 Network 面板的请求 URL/body用 encodeURIComponent 或 URLSearchParams响应中文乱码响应头 charset 不对看 Response Headers 的 Content-Type后端设置charsetutf-8POST JSON 中文乱码请求头缺 charset看 Request Headers加;charsetutf-8表单提交中文乱码页面编码与服务器不一致看页面 meta 和服务器配置统一用 UTF-8我的经验是只要前后端都统一用 UTF-8并且请求头显式声明 charset99% 的乱码问题都能避免。剩下 1% 是服务器中间件或者数据库连接串的编码没设对那就得往后端查了。5.2 请求发出去了但没反应这种情况通常有几个原因跨域被拦截控制台会报 CORS 错误Network 面板里请求状态是(failed)或者CORS error。解决要么后端加 CORS 头要么走开发代理。请求被浏览器缓存GET 请求如果 URL 没变浏览器可能直接返回缓存。可以在 URL 后面加时间戳?_tDate.now()或者后端设置Cache-Control: no-cache。请求被拦截器改了有些框架或者浏览器插件会拦截请求比如广告拦截器可能把包含ad、banner的 URL 拦掉。换个参数名试试。HTTPS 页面请求 HTTP 接口混合内容会被浏览器阻止。统一用 HTTPS 或者相对路径。5.3 回调地狱和顺序问题多个 Ajax 请求有依赖关系时早期写法会嵌套很多层ajax({ url: /api/a, success: resA { ajax({ url: /api/b?id resA.id, success: resB { ajax({ url: /api/c?id resB.id, success: resC { console.log(resC); }}); }}); }});这就是回调地狱。现在用 async/await 可以写成顺序执行的样子async function load() { const resA await request(/api/a); const resB await request(/api/b, { data: { id: resA.id } }); const resC await request(/api/c, { data: { id: resB.id } }); console.log(resC); }如果几个请求互不依赖用Promise.all并发执行能省不少时间const [users, orders, products] await Promise.all([ request(/api/users), request(/api/orders), request(/api/products) ]);注意Promise.all只要有一个失败就整体失败。如果希望部分失败不影响其他结果用Promise.allSettled。5.4 我踩过的几个真实坑坑一GET 请求参数里有数组。比如ids[1,2,3]直接URLSearchParams会变成ids1%2C2%2C3后端可能解析不了。正确做法是展开成ids1ids2ids3或者用ids[]1ids[]2这种约定格式具体看后端怎么接。坑二POST 请求 Content-Type 设了但 body 是空。有些后端框架看到 Content-Type 是 JSON就会去解析 body结果 body 是空字符串直接报解析错误。所以发 POST 时即使没有参数也最好传个{}。坑三并发请求把后端打挂。页面初始化时一次性发几十个请求后端连接池瞬间被打满。解决办法是合并请求、加节流、或者用队列控制并发数。我一般限制同时最多 6 个请求超出的排队。坑四请求没有取消机制。用户快速切换 tab 或者输入搜索词前一个请求还没回来后一个又发出去了结果旧数据覆盖了新数据。用AbortController在发新请求前取消旧请求能避免这个问题。6. 写在最后的一点个人体会Ajax 这个东西说简单也简单一个fetch就能跑起来说复杂也复杂编码、跨域、并发、错误处理每一个都能单独写一篇。我这些年最大的体会是不要迷信封装库但一定要理解底层。axios 再好用遇到诡异问题时还是得打开 Network 面板看请求头、看响应头、看实际发送的 body。另外异步数据传输的核心不是“异步”这个技术本身而是用户体验的连续性。用户不关心你用的是 XMLHttpRequest 还是 fetch他们只关心点了按钮之后页面有没有卡、数据有没有出来、出错了有没有提示。所以做 Ajax 的时候多想想加载状态怎么展示、失败怎么重试、空数据怎么提示这些细节比选什么库重要得多。如果你正在对接泛微 ecology9 或者类似平台我的建议是先用 Postman 或者 curl 把接口调通确认参数格式、鉴权方式、返回结构再往代码里写。直接在前端调试接口变量太多容易把自己绕进去。
返回列表