概述
在网络请求中,超时处理是一个非常重要的环节。当网络不稳定或服务器响应缓慢时,如果没有超时机制,应用程序可能会一直等待,导致用户界面卡死,严重影响用户体验。
Flutter 提供了多种方式来实现请求超时处理,本文将详细介绍这些方法,并提供完整的代码示例。
为什么需要超时处理
在实际开发中,网络请求可能会因为以下原因无法及时完成:
- 网络连接不稳定
- 服务器负载过高
- 网络延迟过大
- 请求的数据量过大
如果没有超时处理,这些情况会导致:
- 用户界面卡死,无法进行任何操作
- 应用程序消耗过多资源
- 用户体验严重下降
- 可能导致应用程序崩溃
因此,合理的超时处理是构建健壮网络请求的必要条件。
使用 Future.timeout() 实现超时
Dart 的Future类提供了timeout()方法,可以为异步操作设置超时时间。
基本用法
import'package:http/http.dart'ashttp;import'dart:convert';Future<dynamic>fetchWithTimeout(Stringurl,{Durationtimeout=constDuration(seconds:10)})async{try{finalresponse=awaithttp.get(Uri.parse(url)).timeout(timeout);returnjsonDecode(response.body);}onTimeoutExceptioncatch(e){throwException('请求超时:$e');}catch(e){throwException('请求失败:$e');}}代码说明
timeout()方法接收一个Duration参数,表示超时时间- 如果在指定时间内
Future没有完成,会抛出TimeoutException - 需要使用
on TimeoutException catch来捕获超时异常 - 其他异常仍然由普通的
catch块处理
实际示例
try{vardata=awaitfetchWithTimeout('https://api.example.com/users',timeout:constDuration(seconds:5),);print('请求成功:$data');}onTimeoutException{print('请求超时,请稍后重试');}catch(e){print('请求失败:$e');}带超时配置的 API 客户端
将超时处理封装到 API 客户端中,可以统一管理超时配置。
实现代码
classTimeoutApiClient{finalStringbaseUrl;finalDurationdefaultTimeout;TimeoutApiClient({requiredthis.baseUrl,this.defaultTimeout=constDuration(seconds:15),});Map<String,String>_buildHeaders(){return{'Content-Type':'application/json','Accept':'application/json',};}Future<dynamic>get(Stringpath,{Duration?timeout,Map<String,String>?query})async{Uriuri=Uri.parse('$baseUrl$path');if(query!=null){uri=uri.replace(queryParameters:query);}try{finalresponse=awaithttp.get(uri,headers:_buildHeaders()).timeout(timeout??defaultTimeout);return_handleResponse(response);}onTimeoutException{throwRequestTimeoutException('GET请求超时');}}Future<dynamic>post(Stringpath,Map<String,dynamic>body,{Duration?timeout})async{try{finalresponse=awaithttp.post(Uri.parse('$baseUrl$path'),headers:_buildHeaders(),body:jsonEncode(body),).timeout(timeout??defaultTimeout);return_handleResponse(response);}onTimeoutException{throwRequestTimeoutException('POST请求超时');}}dynamic_handleResponse(http.Responseresponse){if(response.statusCode>=200&&response.statusCode<300){returnjsonDecode(response.body);}else{throwException('HTTP Error:${response.statusCode}');}}}使用示例
finalapiClient=TimeoutApiClient(baseUrl:'https://api.example.com');// 使用默认超时varuser=awaitapiClient.get('/users/1');// 使用自定义超时vardata=awaitapiClient.get('/large-data',timeout:constDuration(seconds:30));// POST请求varresult=awaitapiClient.post('/users',{'name':'John'});自定义超时异常
创建自定义异常类可以更好地区分不同类型的错误。
实现代码
classRequestTimeoutExceptionimplementsException{finalStringmessage;finalDateTimetimestamp;finalString?endpoint;RequestTimeoutException(this.message,{this.endpoint}):timestamp=DateTime.now();@overrideStringtoString(){return'RequestTimeoutException:$message''${endpoint != null ? '(端点:$endpoint)':''}'' (时间:${timestamp.toIso8601String()})';}}代码说明
message:错误消息timestamp:超时发生的时间endpoint:可选的请求端点信息- 重写
toString()方法提供详细的错误信息
使用示例
try{vardata=awaitapiClient.get('/users');}onRequestTimeoutExceptioncatch(e){print('超时发生在:${e.timestamp}');print('请求端点:${e.endpoint}');// 可以根据端点信息进行不同的处理}catch(e){print('其他错误:$e');}不同请求类型的超时配置
不同类型的请求需要不同的超时时间,创建统一的配置类可以方便管理。
配置类实现
classTimeoutConfig{staticconstDurationshort=Duration(seconds:5);staticconstDurationnormal=Duration(seconds:10);staticconstDurationlong=Duration(seconds:30);staticconstDurationupload=Duration(seconds:60);staticconstDurationdownload=Duration(seconds:120);}适用场景
| 超时配置 | 时间 | 适用场景 |
|---|---|---|
| short | 5秒 | 快速查询、健康检查、简单接口 |
| normal | 10秒 | 普通API请求、数据列表查询 |
| long | 30秒 | 大数据查询、复杂计算、报表生成 |
| upload | 60秒 | 文件上传、表单提交 |
| download | 120秒 | 大文件下载 |
使用示例
finalclient=TimeoutApiClient(baseUrl:'https://api.example.com');// 健康检查 - 使用短超时varhealth=awaitclient.get('/health',timeout:TimeoutConfig.short);// 用户列表 - 使用正常超时varusers=awaitclient.get('/users',timeout:TimeoutConfig.normal);// 生成报表 - 使用长时间超时varreport=awaitclient.get('/report/generate',timeout:TimeoutConfig.long);// 上传文件 - 使用上传超时varresult=awaitclient.post('/upload',data,timeout:TimeoutConfig.upload);超时处理的高级技巧
1. 指数退避重试
在超时后进行重试时,可以使用指数退避策略,避免频繁重试。
Future<dynamic>fetchWithRetry(Stringurl,{int maxRetries=3,DurationinitialDelay=constDuration(seconds:1),})async{int retryCount=0;Durationdelay=initialDelay;while(retryCount<maxRetries){try{finalresponse=awaithttp.get(Uri.parse(url)).timeout(constDuration(seconds:10));returnjsonDecode(response.body);}onTimeoutException{retryCount++;if(retryCount>=maxRetries){throwRequestTimeoutException('请求超时,已重试$maxRetries次');}awaitFuture.delayed(delay);delay=delay*2;}}throwException('重试次数已用尽');}2. 动态超时调整
根据网络状态动态调整超时时间。
Future<dynamic>fetchWithDynamicTimeout(Stringurl)async{Durationtimeout=constDuration(seconds:10);// 检查网络状态,如果是移动网络,使用更短的超时if(await_isMobileNetwork()){timeout=constDuration(seconds:5);}finalresponse=awaithttp.get(Uri.parse(url)).timeout(timeout);returnjsonDecode(response.body);}3. 超时回调
提供超时回调函数,方便在超时时执行特定操作。
Future<dynamic>fetchWithTimeoutCallback(Stringurl,{Durationtimeout=constDuration(seconds:10),voidFunction()?onTimeout,})async{try{finalresponse=awaithttp.get(Uri.parse(url)).timeout(timeout);returnjsonDecode(response.body);}onTimeoutException{onTimeout?.call();throwRequestTimeoutException('请求超时');}}UI 层的超时处理
在 UI 层需要为用户提供友好的超时提示。
示例代码
classTimeoutDemoPageextendsStatefulWidget{constTimeoutDemoPage({super.key});@overrideState<TimeoutDemoPage>createState()=>_TimeoutDemoPageState();}class_TimeoutDemoPageStateextendsState<TimeoutDemoPage>{String_status='准备就绪';bool _loading=false;Future<void>_fetchData()async{setState((){_loading=true;_status='请求中...';});try{finalresponse=awaithttp.get(Uri.parse('https://jsonplaceholder.typicode.com/users/1')).timeout(constDuration(seconds:5));varuser=jsonDecode(response.body);setState((){_status='请求成功!\n用户名: ${user['name']}';});}onTimeoutException{setState((){_status='请求超时! 请检查网络连接或稍后重试';});_showTimeoutDialog();}catch(e){setState((){_status='请求失败:$e';});}finally{setState(()=>_loading=false);}}void_showTimeoutDialog(){showDialog(context:context,builder:(context)=>AlertDialog(title:constText('请求超时'),content:constText('网络请求超时,请检查网络连接后重试'),actions:[TextButton(onPressed:()=>Navigator.pop(context),child:constText('确定'),),],),);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('超时处理示例')),body:Center(child:Column(mainAxisAlignment:MainAxisAlignment.center,children:[ElevatedButton(onPressed:_loading?null:_fetchData,child:_loading?constCircularProgressIndicator():constText('发起请求'),),constSizedBox(height:20),Card(child:Padding(padding:constEdgeInsets.all(16),child:Text(_status),),),],),),);}}超时处理最佳实践
1. 根据请求类型设置不同超时时间
不同类型的请求需要不同的超时配置,不能一刀切。
2. 使用自定义异常类型区分超时错误
自定义异常可以携带更多信息,方便错误处理和日志记录。
3. 在 UI 层显示友好的超时提示
用户需要知道发生了什么,以及应该怎么做。
4. 为关键请求提供重试机制
对于重要的请求,可以在超时后自动重试。
5. 监控超时率优化超时配置
通过监控超时率,不断优化超时配置,找到最佳平衡点。
6. 避免设置过短的超时导致正常请求失败
超时时间设置过短会导致正常请求也失败,需要根据实际情况调整。
7. 考虑网络状态
在移动网络下,超时时间可以适当缩短;在 Wi-Fi 下,可以适当延长。
总结
超时处理是网络请求中不可或缺的一部分,合理的超时配置可以:
- 避免应用程序卡死
- 提升用户体验
- 减少资源浪费
- 提高系统稳定性
通过本文介绍的方法,你可以构建一个健壮的超时处理机制,确保你的应用在各种网络环境下都能正常运行。
完整示例代码
以下是一个完整的超时处理示例:
import'package:http/http.dart'ashttp;import'dart:convert';classTimeoutApiClient{finalStringbaseUrl;finalDurationdefaultTimeout;TimeoutApiClient({requiredthis.baseUrl,this.defaultTimeout=constDuration(seconds:10),});Future<dynamic>get(Stringpath,{Duration?timeout})async{try{finalresponse=awaithttp.get(Uri.parse('$baseUrl$path')).timeout(timeout??defaultTimeout);returnjsonDecode(response.body);}onTimeoutException{throwRequestTimeoutException('GET请求超时');}}Future<dynamic>post(Stringpath,Map<String,dynamic>body,{Duration?timeout})async{try{finalresponse=awaithttp.post(Uri.parse('$baseUrl$path'),headers:{'Content-Type':'application/json'},body:jsonEncode(body),).timeout(timeout??defaultTimeout);returnjsonDecode(response.body);}onTimeoutException{throwRequestTimeoutException('POST请求超时');}}}classRequestTimeoutExceptionimplementsException{finalStringmessage;finalDateTimetimestamp;RequestTimeoutException(this.message):timestamp=DateTime.now();@overrideStringtoString(){return'RequestTimeoutException:$message(${timestamp.toIso8601String()})';}}classTimeoutConfig{staticconstDurationshort=Duration(seconds:5);staticconstDurationnormal=Duration(seconds:10);staticconstDurationlong=Duration(seconds:30);}通过这个示例,你可以快速实现一个带有超时处理的 API 客户端,为你的网络请求提供可靠的超时保护。