You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}");
}

尝试过的无效方法

  1. 将两个await分别放在独立的try-catch块中,仍出现崩溃。
  2. 使用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;
});

问题原因

  1. 未处理的异步异常:当第一个Future抛出异常被捕获后,第二个Future已在后台执行,其抛出的异常会成为未处理的异步异常,导致程序崩溃。
  2. 递归调用的异常泄漏: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 15:50:30