HarmonyOS 网络请求稳定性实战:超时、重试、弱网兜底与状态恢复
网络请求失败不一定是接口挂了。移动场景里更常见的是弱网抖动、切后台、DNS 延迟、服务端偶发 5xx、用户重复点击、页面销毁后回调仍然更新状态。稳定的网络层不能只封装一个 request,还要处理超时、错误分类、重试、缓存兜底和页面状态恢复。

一、先定义请求上下文
请求上下文要包含业务场景、超时、重试次数和缓存策略。这样同一个网络层可以服务不同页面。
| 首页列表 | 可重试,可读缓存 |
| 提交表单 | 不自动重试,避免重复提交 |
| 搜索建议 | 短超时,失败静默 |
| 详情页 | 可重试,失败展示重试按钮 |

二、版本与边界说明
本文以 HarmonyOS / ArkTS 网络请求封装为背景,示例不绑定具体服务端协议。真实项目可以接入 @ohos.net.http、团队 HTTP SDK 或网关层,但请求上下文、错误映射和恢复策略应保持独立。
建议记录以下环境:
| API 版本 | 当前 HarmonyOS SDK |
| 网络库 | 系统 HTTP 或团队封装 |
| 服务端协议 | REST、GraphQL 或自定义协议 |
| 缓存层 | 内存、Preferences、文件或数据库 |
三、请求模型:不要只传 URL
只传 URL 会让网络层不知道该不该重试、该不该缓存、失败后怎么提示。
export interface RequestContext {
id: string;
url: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
timeoutMs: number;
retryTimes: number;
cacheKey?: string;
scene: 'list' | 'detail' | 'submit' | 'search';
}
代码解释:

四、错误分类:不是所有失败都能重试
4xx 多半是客户端参数或权限问题,盲目重试没有意义;网络断开和 5xx 才更适合重试。
export type RequestErrorType = 'network' | 'timeout' | 'server' | 'client' | 'business';
export function canRetry(type: RequestErrorType): boolean {
return type === 'network' || type === 'timeout' || type === 'server';
}
代码解释:
五、指数退避:重试要克制
重试不能立刻连续打三次,否则弱网下会加重拥塞。指数退避能给网络恢复时间。
export function getRetryDelay(attempt: number): number {
const base = 500;
const max = 8000;
return Math.min(base * Math.pow(2, attempt), max);
}
代码解释:
六、缓存兜底:失败时保留可用内容
列表和详情页可以在请求失败时读取上一次成功缓存,让页面不至于空白。
export interface CacheRecord<T> {
value: T;
savedAt: number;
}
export function isCacheFresh<T>(record: CacheRecord<T>, ttlMs: number): boolean {
return Date.now() – record.savedAt <= ttlMs;
}
代码解释:
七、页面状态恢复:请求失败也要有确定状态
页面状态不能只有成功和加载。弱网下要能表达缓存、失败、重试中和空数据。
export type PageDataState = 'idle' | 'loading' | 'success' | 'cacheFallback' | 'failed';
export interface NetworkPageState<T> {
state: PageDataState;
data?: T;
message?: string;
}
代码解释:
八、防止过期回调:页面销毁后不更新状态
网络请求返回时,页面可能已经退出。需要用请求 id 或生命周期标记避免旧回调覆盖新状态。
export class RequestToken {
private active = true;
cancel(): void {
this.active = false;
}
canUpdate(): boolean {
return this.active;
}
}
代码解释:
九、验证流程
1. 正常网络打开列表,确认请求成功并写入缓存。
2. 切到弱网,确认超时和重试策略生效。
3. 断网后进入页面,确认可读取缓存兜底。
4. 表单提交失败时确认不会自动重复提交。
5. 快速进入再退出页面,确认旧回调不更新 UI。
6. 查看日志,确认错误类型和重试次数可追踪。
十、常见问题排查
| 弱网一直转圈 | 没有超时 | 设置 timeoutMs |
| 重复提交订单 | POST 自动重试 | 提交类禁用自动重试 |
| 失败页面空白 | 没有缓存兜底 | 增加 cacheFallback |
| 返回后崩溃 | 旧回调更新页面 | 加入 RequestToken |
| 错误提示混乱 | 未分类错误 | 使用 RequestErrorType |
十一、验收清单
| 请求有超时配置 | |
| 错误类型能区分 | |
| 可重试和不可重试场景分开 | |
| 缓存兜底有过期时间 | |
| 页面销毁后不会更新状态 |
弱网验收要覆盖断网、慢网、请求中切后台、请求中返回上一页。只测正常 Wi-Fi,网络层问题基本看不出来。
还要看“用户重复触发”。比如用户连续下拉刷新、连续点击提交按钮、搜索框快速输入,这些场景会产生多个并发请求。如果没有请求 token、节流或取消机制,旧请求可能覆盖新结果,页面就会出现数据倒退。
十二、工程落地建议
可以先从首页列表接入稳定请求链路,因为它既需要加载速度,也需要弱网兜底。首页跑通后,再把详情页、搜索页和提交页按各自策略接入,不要所有请求套同一套重试规则。
建议把网络请求分成三类治理:查询类、提交类、实时类。查询类可以重试和读缓存,提交类必须防重复,实时类要关注连接状态和恢复策略。分类之后,网络层的默认策略才不会误伤业务。
上线后可以记录脱敏指标:请求耗时、错误类型、重试次数、缓存命中、最终状态。这些指标能帮助团队判断问题到底来自服务端、网络环境还是客户端状态管理。不要只记录“请求失败”,那样无法定位。
如果服务端支持幂等键,提交类请求最好带上业务幂等标识。这样即使用户重复点击或网络层发生重试,服务端也能识别同一笔业务操作,避免重复创建订单、重复收藏或重复提交表单。
弱网兜底也要避免过度乐观。缓存数据应有时间标识,页面可以提示“当前展示上次成功加载的数据”。让用户知道数据来源,比悄悄展示过期内容更可靠。
并发请求也要治理。搜索框输入、筛选条件切换、下拉刷新都可能在短时间内发起多次请求。建议给每个请求带上序号,页面只接受最后一次请求的结果。否则旧请求慢一步返回,就可能覆盖新数据。
export class LatestRequestGuard {
private latestId = 0;
next(): number {
this.latestId += 1;
return this.latestId;
}
isLatest(id: number): boolean {
return id === this.latestId;
}
}
代码解释:
网络层还要和页面生命周期配合。页面退出时不一定能取消底层请求,但至少不能让回调继续更新 UI。RequestToken 和 LatestRequestGuard 可以组合使用:前者处理页面是否还活着,后者处理结果是否过期。
对于缓存兜底,要区分“可展示缓存”和“可提交缓存”。列表、详情、资讯内容可以展示缓存;支付结果、库存状态、权限状态不能随便用旧数据兜底。不同数据类型要有明确规则。
发布前还要检查错误文案。网络错误、服务端错误、登录失效、业务失败应该给不同提示。用户看到“网络异常”却实际是登录过期,会误判问题,也会增加客服成本。
错误文案最好和错误类型一一对应,避免所有失败都落到同一个 Toast。
这会直接影响用户是否愿意再次尝试。
十三、总结
网络稳定性的核心不是多重试几次,而是让请求行为可预测:超时有边界、错误能分类、重试有节制、缓存能兜底、页面状态可恢复。做到这些,弱网体验才会从“碰运气”变成可治理。






