所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

别再乱堆Ajax代码!用Promise+参数解构封装请求的超实用方法

别再乱堆Ajax代码!用Promise+参数解构封装请求的超实用方法 一

文章目录CloseOpen

其实解决方法很简单:用Promise+参数解构把Ajax请求“打包”成通用工具函数。不用再重复写xhr初始化、状态判断,不用再面对“回调地狱”——我们把请求的url、参数、 headers用解构语法简化配置,用Promise处理异步结果,再加上统一的错误拦截和 loading 状态管理。不管是GET数据还是POST表单,调用时只需要一行代码,参数清晰得像填表格。

封装后的请求有多香?以前要写20行的重复代码,现在3行搞定;以前改接口要改5个文件,现在只改1处;连新人接手项目,看函数名就知道怎么调用。这篇文章不搞虚的,手把手教你从0到1实现封装,帮你彻底告别“乱堆Ajax”的日子,写出更干净、更省心的前端代码。

你有没有过这种经历?写一个简单的用户登录功能,既要手动初始化XMLHttpRequest,又要写onreadystatechange的状态判断,刚解决完“404找不到接口”的问题,又发现“POST请求的data没转JSON”——等终于把登录弄好,要写注册请求时,只能复制粘贴之前的代码,改一改url和data,结果改着改着,某个请求的headers漏加了Authorization,或者状态码判断成了200而不是201,排查半天才发现是重复代码里的小疏漏?

我猜很多前端同学都碰过这种麻烦——不是我们写不好Ajax,是重复的代码太多,把精力都耗在“复制-粘贴-改bug”上了。去年我帮朋友的生鲜电商小程序做优化时,他们的前端代码里藏着12个“差不多”的Ajax请求:有的用了xhr,有的用了fetch,有的加了loading,有的没加,每次改接口地址要翻遍3个页面,改5遍代码。我用“Promise+参数解构”把这些请求封装成一个通用函数后,他们的开发效率直接提升了30%——用他们的话说:“现在写请求像填表格,再也不用记‘open的第三个参数是不是true’了。”

为什么要封装?不是“多此一举”,是“省大心”

在聊怎么封装之前,得先想清楚:我们到底在烦Ajax的什么?

无非三个痛点:

  • 重复代码多:每个请求都要写xhr.open()、xhr.send()、状态判断,甚至错误提示,改一个配置要改N遍;
  • 异步处理乱:用回调函数处理结果,遇到“先请求用户信息,再请求订单”的场景,容易变成“回调地狱”(三层嵌套的函数,代码像金字塔);
  • 维护成本高:某个请求的错误处理逻辑变了,要找遍所有用到这个请求的地方——比如之前项目里,接口突然要求所有请求加一个X-App-Version的header,我朋友的团队花了半天时间才改完所有请求。
  • 而封装的核心,就是把这些“重复的、通用的逻辑”抽出来,变成一个“只需要传必要参数”的工具函数。就像你家里的“收纳盒”:把零散的袜子、内衣分类装起来,找的时候不用翻遍整个衣柜——封装就是Ajax的“收纳盒”

    用Promise+参数解构,把Ajax“装”成“好用的工具”

    接下来直接上干货:怎么用Promise+参数解构写一个“通吃”的请求函数? 我会用最直白的话,把每一步的逻辑讲清楚——毕竟“能听懂、能复制”才是实用的关键。

    第一步:用“参数解构”把配置变“简单”

    Ajax的配置项其实就那么几个:url(接口地址)、method(请求方法,比如GET/POST)、data(请求数据)、headers(请求头)。以前我们写请求,要把这些配置项一个一个传给函数,比如:

    function request(url, method, data, headers) { ... }

    但调用的时候要记顺序,比如request('/api/login', 'POST', { name: 'admin' }, { 'Content-Type': 'json' })——万一记混了顺序,把method写成data,就会出大问题。

    ES6的参数解构就能解决这个问题:把配置项写成一个对象,函数里用解构赋值提取需要的属性。比如:

    function request({
    

    url, // 必传:接口地址

    method = 'GET', // 可选:默认GET方法

    data = {}, // 可选:默认空数据

    headers = { // 可选:默认请求头

    'Content-Type': 'application/json'

    }

    }) {

    // 这里写请求逻辑

    }

    这样调用的时候,只需要传“必要的参数”,比如:

  • 要发一个GET请求,只需要request({ url: '/api/user' })
  • 要发一个带data的POST请求,就是request({ url: '/api/login', method: 'POST', data: { name: 'admin' } })
  • 要加自定义header,就加headers: { 'Authorization': 'Bearer token' }
  • 是不是像“填表格”?不用记参数顺序,也不用写多余的默认值——这就是参数解构的妙处:把“必须记顺序的参数”变成“明确属性的对象”,谁看了都懂。

    第二步:用“Promise”把异步变“清爽”

    以前处理Ajax的异步结果,要写xhr.onreadystatechange的回调函数,比如:

    xhr.onreadystatechange = function() {
    

    if (xhr.readyState === 4) {

    if (xhr.status === 200) {

    const res = JSON.parse(xhr.responseText);

    // 处理成功结果

    } else {

    // 处理错误

    }

    }

    };

    如果要做“先请求用户信息,再请求订单”,就得把第二个请求放进第一个的成功回调里,变成:

    // 回调地狱示例
    

    xhr1.onreadystatechange = function() {

    if (xhr1.readyState === 4 && xhr1.status === 200) {

    const user = JSON.parse(xhr1.responseText);

    xhr2.open('GET', /api/orders?userId=${user.id});

    xhr2.onreadystatechange = function() {

    if (xhr2.readyState === 4 && xhr2.status === 200) {

    const orders = JSON.parse(xhr2.responseText);

    // 再请求订单详情...

    }

    };

    xhr2.send();

    }

    };

    这种“嵌套”的代码,看久了眼睛都花——而Promise就是解决这个问题的“钥匙”

    我们可以把Ajax的异步操作“包”进Promise里,用resolve传递成功结果,用reject传递错误:

    function request({ url, method = 'GET', data = {}, headers = {} }) {
    

    return new Promise((resolve, reject) => {

    const xhr = new XMLHttpRequest();

    //

  • 初始化请求
  • xhr.open(method, url);

    //

  • 设置请求头
  • Object.entries(headers).forEach(([key, value]) => {

    xhr.setRequestHeader(key, value);

    });

    //

  • 处理响应
  • xhr.onload = function() {

    if (xhr.status >= 200 && xhr.status < 300) {

    // 成功:解析响应数据

    try {

    resolve(JSON.parse(xhr.responseText));

    } catch (e) {

    // 万一响应不是JSON(比如文本),直接返回原内容

    resolve(xhr.responseText);

    }

    } else {

    // 失败:抛出错误

    reject(new Error(请求失败:状态码${xhr.status},信息${xhr.statusText}));

    }

    };

    //

  • 处理网络错误
  • xhr.onerror = function() {

    reject(new Error('网络错误,请检查连接'));

    };

    //

  • 发送请求(处理data)
  • if (method === 'GET') {

    // GET请求:把data转成查询字符串,拼在url后面

    const params = new URLSearchParams(data).toString();

    xhr.send(params ? ${url}?${params} url);

    } else {

    // POST/PUT等:把data转成JSON字符串

    xhr.send(JSON.stringify(data));

    }

    });

    }

    这样封装后,调用请求就变成了链式调用或者async/await,代码平平整整:

    // 用then/catch处理
    

    request({ url: '/api/user' })

    .then(res => console.log('用户信息:', res))

    .catch(err => alert('请求失败:' + err.message));

    // 用async/await(更简洁)

    async function getOrders() {

    try {

    const user = await request({ url: '/api/user' });

    const orders = await request({ url: /api/orders?userId=${user.id} });

    console.log('用户订单:', orders);

    } catch (err) {

    alert('获取订单失败:' + err.message);

    }

    }

    是不是比“回调嵌套”清爽10倍?

    第二步:加“统一处理”,把“零散的逻辑”聚起来

    封装的好处远不止“简化调用”——我们还能在函数里加“统一的逻辑”,比如:

  • 统一loading状态:请求开始时显示loading,结束时隐藏——不用每个请求都写showLoading()hideLoading()
  • 统一错误提示:所有请求的错误都弹出相同样式的提示框,不用每个请求都写alert()
  • 统一拦截器:比如接口要求所有请求加X-Token,或者遇到401(未授权)自动跳登录页——只需要在封装函数里加一行代码。
  • 比如我之前做的项目里,给request函数加了“统一loading”:

    function request(config) {
    

    // 请求开始:显示loading

    const loading = document.getElementById('loading');

    loading.style.display = 'block';

    return new Promise((resolve, reject) => {

    // ... 原来的请求逻辑 ...

    xhr.onload = function() {

    // 请求结束:隐藏loading

    loading.style.display = 'none';

    // ... 原来的状态判断 ...

    };

    xhr.onerror = function() {

    // 网络错误也隐藏loading

    loading.style.display = 'none';

    reject(new Error('网络错误'));

    };

    });

    }

    就这几行代码,帮项目里的10个请求省了20行重复的loading逻辑——这才是封装的“精髓”:把“重复做的事”变成“一次做对”

    封装后有多香?看一组“真实对比”

    为了让你更直观,我把“封装前”和“封装后”的开发成本做了个对比(数据来自去年帮朋友做的项目):

    操作场景 封装前代码量 封装后代码量 修改时间
    登录请求 25行 3行 5分钟
    注册请求 22行 3行 3分钟
    修改用户信息 20行 3行 2分钟
    新增收货地址 18行 3行 1分钟

    朋友说,以前改一个接口要花1小时,现在只需要10分钟——这不是“技术牛逼”,是“用对了方法”

    最后想说:封装不是“炫技”,是“对自己好一点”

    很多同学觉得“封装麻烦”,“不如直接写来的快”——但你有没有算过:第一次写封装函数花30分钟,后面10个请求能省2小时,这笔账是不是很划算?

    我自己的习惯是,每个项目开工前,先写好这个请求封装函数——不管项目大小,都能省不少心。去年做的一个H5活动页,只有3个请求,我用这个函数封装后,写请求只用了10分钟,剩下的时间用来优化动画效果,客户看了直接加了10%的尾款。

    你如果还没试过,不妨今天就拿出项目里的一个Ajax请求,按照我写的代码改一改——不用怕出错,MDN文档里有详细的参数解构和Promise说明(参考链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promisenofollow),遇到问题查一查就行

    等你用顺了,一定会回来跟我说:“原来写请求可以这么爽!”

    对了,如果你封装的时候遇到“data转查询字符串不对”或者“headers没生效”的问题,欢迎在评论区留言——我踩过的坑,说不定能帮你省半小时排查时间。


    为什么要用Promise+参数解构来封装Ajax啊?

    主要是解决Ajax的三个痛点:重复代码多、异步处理乱、维护成本高。比如以前每个请求都要写xhr.open、状态判断,改个接口地址得翻遍好几个文件;用回调函数处理异步结果,遇到“先请求用户信息再请求订单”的场景,容易变成三层嵌套的“回调地狱”;要是接口突然要求加个header,得改遍所有请求。而Promise能把异步操作变成链式调用或async/await,解决嵌套问题;参数解构是把配置写成对象,不用记参数顺序,只传必要的url、method、data就行,像填表格一样清晰。

    比如去年帮朋友的生鲜小程序优化,他们12个“差不多”的Ajax请求,封装后开发效率直接提了30%——改接口地址只需要改1处,再也不用复制粘贴后找漏改的bug了。

    封装后的请求函数,调用起来真的比直接写Ajax简单吗?

    真的简单太多!以前写个登录请求要20行代码:初始化xhr、设置headers、处理onreadystatechange、转JSON数据,现在调用封装好的函数,只需要一行代码——比如request({url: ‘/api/login’, method: ‘POST’, data: {name: ‘admin’}}),参数一目了然。

    而且新人接手也不用学,看函数参数就知道怎么传,再也不会出现“把method写成data”“漏加Authorization header”这种低级错误。以前改个接口要花半天,现在10分钟就能搞定,朋友团队说“现在写请求像填表格,再也不用记xhr的参数顺序了”。

    封装的时候,GET和POST请求的data处理要注意什么?

    GET请求的话,要把data转成查询字符串拼在url后面——比如用URLSearchParams把data对象转成“key=value&key2=value2”的形式,再拼到url后面;POST请求就得把data转成JSON字符串,因为大部分后端接口要求POST的数据是JSON格式的。

    比如封装函数里,GET请求会处理params:const params = new URLSearchParams(data).toString();然后xhr.send(params ? ${url}?${params} url);POST请求直接send(JSON.stringify(data)),这样就不用每个请求都手动处理data格式,避免“POST请求的data没转JSON”这种排查半天的错误。

    封装后的函数遇到错误,比如网络问题或者状态码不对,怎么处理啊?

    封装的时候用Promise的reject处理错误:比如状态码不是200-299的时候,会抛出“请求失败:状态码XX”的错误;网络错误的话,xhr.onerror会触发“网络错误,请检查连接”的错误。调用的时候用catch或者try/catch捕获就行,比如request(…).catch(err => alert(err.message))。

    还能加统一处理,比如所有请求都加loading——请求开始显示loading,结束或错误时隐藏;或者统一错误提示样式,不用每个请求都写alert。比如之前做H5活动页,封装函数里加了统一loading,3个请求省了20行重复代码,剩下的时间用来优化动画,客户还加了10%尾款。

    原文链接:https://www.mayiym.com/50908.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码