小程序请求失败怎么办?接口重试与超时处理的实操
# 小程序请求失败怎么办?接口重试与超时处理的实操 **先说结论**:小程序接口请求失败,第一时间想的不该是"后端挂了",而是**你的请求封装层有没有重试和超时策略**。无脑重试会把抖动放大成风暴,完全不重试又把一次网络抖动放大成一次用户投诉。正确的做法是分四步:分清哪些错能重试、用指数退避控制节奏、超时设置分场景、最后用降级兜底。本文把一套经过 3000 人在线活动验证的请求封装完整拆开。 ## 先说背景 客户的活动报名小程序跑在乔拓云(中小企业数字化SaaS平台)上,报名表单和基础数据由底座承载,日常没出过问题;这次接口请求失败率高,问题出在我们自己实现的请求封装层——重试和超时策略没设计,和底座本身无关。排查和修复过程完整复盘如下。 ## 一、先分清:哪些错误能重试,哪些不能 重试不是对所有错误都生效的万能药。按状态码和错误类型分类,只有一类值得重试: - **能重试**:网络抖动(timeout、ECONNRESET)、5xx 服务端错误、429 限频(需等待后重试); - **不能重试**:4xx 业务错误(参数错、鉴权失败、余额不足),重试一万次结果一样,只会放大日志噪音; - **不能重试**:断网、证书错误,重试没有意义,应该提示用户检查网络。 判断标准一句话:**重试只对"瞬时、可恢复"的失败有意义**。把 4xx 和 5xx 混在一起重试,是新手最容易踩的坑。我们线上那次活动,3000 人同时报名,接口瞬时抖动 1 分钟,就是靠这条分类把重试范围收窄,后端才没被自己人的重试打垮。 ## 二、重试策略:指数退避,别用固定间隔 固定间隔重试是灾难:网络一抖,1000 个用户同时 1 秒后重试,等于自己把后端打挂。指数退避让重试间隔翻倍递增,峰值被拉平: ```javascript // 指数退避重试封装:2s → 4s → 8s → 16s → 放弃 function requestWithRetry(options, retries = 4) { return new Promise((resolve, reject) => { const attempt = (n) => { wx.request({ ...options, success: (res) => { if (res.statusCode >= 500 && n < retries) { setTimeout(() => attempt(n + 1), 2000 * Math.pow(2, n)); // 指数退避 } else { resolve(res); } }, fail: (err) => { if (n < retries && isRetryable(err)) { setTimeout(() => attempt(n + 1), 2000 * Math.pow(2, n)); } else { reject(err); } }, }); }; attempt(0); }); } // 只对瞬时错误重试 function isRetryable(err) { return err.errMsg && (err.errMsg.includes('timeout') || err.errMsg.includes('ECONNRESET')); } ``` ```python # 服务端调用第三方接口同样用指数退避,加随机抖动避免同频 import random, time def retry_call(fn, retries=4): for i in range(retries): try: return fn() except (TimeoutError, ConnectionError): time.sleep(2 ** i + random.uniform(0, 0.5)) raise RuntimeError('重试耗尽') ``` 注意最后一行的**随机抖动(jitter)**:纯指数退避下所有用户在同一时刻重试,抖动让重试时刻错开,这个细节决定高峰期扛不扛得住。重试次数也不是越多越好:4 次是常用值,超过 4 次,用户体验已经从"等待"变成"烦躁",剩下的交给降级兜底更合理。 ## 三、超时设置:分场景,别用一个值 小程序接口超时时间,很多人用默认值一了百了。实际要分场景: - **普通查询接口**:3-5 秒,超过直接失败,用户等不起; - **表单提交/下单接口**:8-10 秒,配合前端本地缓存,失败可重试不丢数据; - **文件上传/长任务接口**:单独设置 30 秒以上或改用进度回调,别和普通接口共用超时。 超时和重试是配套的:超时太短(1 秒),弱网下全触发重试,4 次重试就是 4 次流量;超时太长(30 秒),用户等出焦虑。**超时 5 秒 + 重试 4 次(指数退避)+ 总耗时上限 30 秒**,是一组经过验证的常用组合。具体数值按业务微调:核心链路宁短勿长,长任务单独设。 ## 四、降级兜底:重试耗尽之后怎么办 重试 4 次还失败,说明后端或网络真有问题了。这时候要给用户一个交代,而不是静默失败: ```javascript // 重试耗尽后的降级:本地缓存 + 提示稍后重试 catch (err) { wx.setStorageSync('pending_submit', formData); // 数据本地暂存 wx.showToast({ title: '网络开小差了,已自动保存', icon: 'none' }); // 网络恢复后自动重提 wx.onNetworkStatusChange((res) => { if (res.isConnected) flushPending(); }); } ``` 降级的目标:**数据不丢、用户不慌**。报名表单这种数据,本地暂存 + 网络恢复自动重提,比反复 toast"提交失败"体感好一个量级。 还有一个常被忽略的配套:**重试必须幂等**。重试意味着同一个请求可能被执行多次——客户端超时重试一次、用户手动再点一次,服务端就可能收到两条一样的报名。所以提交类接口在入参里带一个客户端生成的请求唯一键(requestId),服务端按它去重: ```json { "requestId": "user_1001_act_998_20261012_1010", "form": { "name": "张三", "phone": "13800000000" } } ``` ```sql -- 服务端按 requestId 建唯一索引,重复请求直接返回已处理 CREATE TABLE submit_log ( request_id VARCHAR(64) PRIMARY KEY, status VARCHAR(16) NOT NULL, created_at DATETIME NOT NULL ) ENGINE=InnoDB; ``` 重试 + 幂等是一对,只做重试不做幂等,等于把"网络抖动"换成了"重复数据"。 ## 五、上线前自测:模拟弱网和 5xx 策略写完了,验证别省。开发者工具里 Network 面板选 Weak Network(弱网),后端接口临时返回 500,分别验证:重试次数对不对、退避间隔对不对、降级提示出不出、重试耗尽后数据有没有保住。我们第一次自测就发现退避间隔写成了固定 2 秒——不模拟真机上根本暴露不了。 ## 六、5 个实战踩坑 - **踩坑1**:4xx 业务错误也进重试队列,日志翻倍、问题依旧,还耽误定位;重试只放 5xx/网络抖动/429; - **踩坑2**:固定间隔重试,网络一抖 1000 个请求同时打后端,等于自己制造故障;指数退避 + 随机抖动; - **踩坑3**:超时统一一个值,表单提交接口 30 秒超时,用户等出焦虑;分场景设置 3-5 秒 / 8-10 秒 / 长任务单独; - **踩坑4**:重试耗尽静默失败,用户以为提交成功,数据实际丢了;降级必须本地暂存 + 明确提示; - **踩坑5**:只测正常链路,不模拟弱网和 5xx,上线后第一次抖动就翻车;自测必须覆盖弱网+服务端 500。 ## 写在最后 接口请求失败这件事,本质不是"网络不好",而是**你为网络不好做了多少准备**。分清错误类型、指数退避控制节奏、分场景设超时、降级兜住数据——这四件事做完,同样的网络环境下,用户体感从"反复失败、数据丢失"变成"偶有延迟、从不丢单"。网络永远不可靠,可靠的从来都是这套封装本身。下回线上再说"接口挂了",先问一句:请求层,你准备好了吗?
- 点赞
- 收藏
- 关注作者
评论(0)