Dart/Flutter双API请求SocketException无法全部捕获求助
问题
同时调用两个返回Future的API请求(getRandomImage()和getRandomFact())以并行获取数据,无网络时两个请求都会抛出SocketException,但无论使用顺序await还是Future.wait(),都只能捕获其中一个异常,程序会因未处理的异常崩溃,错误提示:_ClientSocketException (Failed host lookup: 'APIs BASE_URL')。
相关代码
home_page.dart
Future<void> setRandomImageAndFact() async { setState(() { json = imageBytes = null; }); Future<Uint8List> bytesImageFuture = getRandomImage(); Future<Map<String, dynamic>> decodedJsonFuture = getRandomFact(); try{ imageBytes = await bytesImageFuture; json = await decodedJsonFuture; } on SocketException { socketException = true; } setState(() {}); }
images.dart
Future<Uint8List> getRandomImage() async { Uri url = Uri.https(BASE_URL, "cat"); http.Response response; try { response = await http.get(url); } on SocketException { rethrow; } if (response.statusCode == 200) { return response.bodyBytes; } throw Exception("Could not get random image ${response.statusCode}"); }
facts.dart
Future<Map<String, dynamic>> getRandomFact() async { Uri url = Uri.https(BASE_URL, "facts/random"); Future<http.Response> responseFuture = http.get(url); Future<List<String>> rejectedListFuture = loadPrefs(Mode.rejected.value); List<String> rejectedList = await rejectedListFuture; http.Response response; try { response = await responseFuture; } on SocketException { rethrow; } if (response.statusCode == 200) { Map<String, dynamic> decodedJson = jsonDecode(response.body); if (rejectedList.every((element) => element != decodedJson['_id'])) { return decodedJson; } else { return getRandomFact(); } } throw Exception("Could not get random fact ${response.statusCode}"); }
尝试过的无效方法
- 将两个
await分别放在独立的try-catch块中,仍出现崩溃。 - 使用
Future.wait()结合catchError,依旧会因未处理异常崩溃:
Future.wait([ getRandomImage(), getRandomFact(), ]).then((value) { imageBytes = value[0] as Uint8List; json = value[1] as Map<String, dynamic>; }).catchError((e) { socketException = true; });
问题原因
- 未处理的异步异常:当第一个Future抛出异常被捕获后,第二个Future已在后台执行,其抛出的异常会成为未处理的异步异常,导致程序崩溃。
- 递归调用的异常泄漏:
getRandomFact()中存在递归逻辑,无网络时每次递归都会抛出SocketException,这些递归产生的Future异常无法被上层的单次catch捕获,最终导致崩溃。
解决方案
方案1:为每个Future单独处理异常
给每个API请求的Future添加独立的catchError,确保所有异常都能被捕获:
Future<void> setRandomImageAndFact() async { setState(() { json = imageBytes = null; socketException = false; }); // 单独处理图片请求的异常 final imageFuture = getRandomImage().catchError((e) { if (e is SocketException || e.toString().contains('Failed host lookup')) { socketException = true; } return null; // 返回默认值,让Future正常完成 }); // 单独处理事实请求的异常 final factFuture = getRandomFact().catchError((e) { if (e is SocketException || e.toString().contains('Failed host lookup')) { socketException = true; } return null; }); imageBytes = await imageFuture; json = await factFuture; setState(() {}); }
方案2:修复递归逻辑,避免异常泄漏
将getRandomFact()中的递归改为循环,避免多次递归抛出未被捕获的异常:
Future<Map<String, dynamic>> getRandomFact() async { Uri url = Uri.https(BASE_URL, "facts/random"); List<String> rejectedList = await loadPrefs(Mode.rejected.value); while (true) { http.Response response; try { response = await http.get(url); } on SocketException { rethrow; } if (response.statusCode == 200) { Map<String, dynamic> decodedJson = jsonDecode(response.body); // 不在拒绝列表则返回结果 if (!rejectedList.contains(decodedJson['_id'])) { return decodedJson; } // 在列表中则继续循环请求 } else { throw Exception("Could not get random fact ${response.statusCode}"); } } }
方案3:统一捕获所有网络相关异常
由于_ClientSocketException是SocketException的子类,直接捕获SocketException可能无法覆盖所有情况,可改为捕获通用Exception并判断错误信息:
Future<void> setRandomImageAndFact() async { setState(() { json = imageBytes = null; socketException = false; }); try { final results = await Future.wait([ getRandomImage(), getRandomFact(), ]); imageBytes = results[0]; json = results[1]; } catch (e) { // 判断是否为网络相关异常 if (e is SocketException || e.toString().contains('Failed host lookup')) { socketException = true; } else { // 非网络异常继续抛出 rethrow; } } setState(() {}); }
内容的提问来源于stack exchange,提问作者Ahmed Gado
相关产品推荐
相关产品推荐

