Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 实战案例:网络请求中的错误处理

实战案例:网络请求中的错误处理

在前两节中,我们学习了try/await的基本用法和异步任务的异常处理策略。本节将通过一个真实的实战案例——构建一个新闻应用的网络请求模块——将这些知识应用于实践。我们将实现从API获取新闻列表的功能,处理各种潜在错误,并确保代码健壮且用户友好。通过这个案例,你将看到如何将理论转化为可运行的代码。

案例背景

假设我们要开发一个新闻应用,需要从服务器获取最新头条。API地址为https://api.example.com/news,返回JSON格式的新闻列表。我们需要:

  • 获取数据并解析为新闻对象。
  • 处理网络错误、解析错误等异常。
  • 在主线程更新UI。
  • 提供用户反馈(如错误提示)。

以下是逐步实现的过程。

实现步骤

1. 定义数据模型和错误类型

首先定义新闻模型和可能的错误:

struct NewsItem: Codable {
    let id: String
    let title: String
    let summary: String
}

enum NewsError: Error {
    case invalidURL
    case networkFailure(Error)
    case invalidResponse(Int)
    case decodingFailure(Error)
    case emptyData
}
  • NewsItem:新闻条目,符合Codable协议以解析JSON。
  • NewsError:枚举覆盖常见异常。

2. 实现网络请求函数

设计一个异步函数获取新闻:

func fetchNews() async throws -> [NewsItem] {
    guard let url = URL(string: "https://api.example.com/news") else {
        throw NewsError.invalidURL
    }
    
    let (data, response) = try await URLSession.shared.data(from: url)
    guard let httpResponse = response as? HTTPURLResponse else {
        throw NewsError.invalidResponse(0)
    }
    guard httpResponse.statusCode == 200 else {
        throw NewsError.invalidResponse(httpResponse.statusCode)
    }
    guard !data.isEmpty else {
        throw NewsError.emptyData
    }
    
    do {
        return try JSONDecoder().decode([NewsItem].self, from: data)
    } catch {
        throw NewsError.decodingFailure(error)
    }
}
  • 检查URL有效性。
  • 使用try await获取数据,验证响应状态。
  • 解析JSON并抛出特定错误。

3. 添加重试机制

为网络错误添加重试逻辑:

func fetchNewsWithRetry(maxAttempts: Int = 3) async throws -> [NewsItem] {
    var lastError: Error?
    for attempt in 1...maxAttempts {
        do {
            return try await fetchNews()
        } catch NewsError.networkFailure(let error) {
            lastError = error
            if attempt < maxAttempts {
                let delay = UInt64(attempt) * 1_000_000_000 // 指数退避
                try? await Task.sleep(nanoseconds: delay)
                print("第\(attempt)次重试...")
            }
        } catch {
            throw error // 非网络错误直接抛出
        }
    }
    throw NewsError.networkFailure(lastError ?? URLError(.unknown))
}
  • 仅对网络错误重试,其他错误(如解析失败)直接抛出。
  • 使用指数退避增加延迟。

4. 集成到视图控制器

将请求整合到UI更新中:

class NewsViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var errorLabel: UILabel!
    private var newsItems: [NewsItem] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        loadNews()
    }

    private func loadNews() {
        Task { @MainActor in
            do {
                newsItems = try await fetchNewsWithRetry()
                tableView.reloadData()
                errorLabel.isHidden = true
            } catch NewsError.invalidURL {
                showError("无效的URL,请检查配置")
            } catch NewsError.networkFailure(let error) {
                showError("网络错误:\(error.localizedDescription)")
            } catch NewsError.invalidResponse(let code) {
                showError("服务器响应错误,状态码:\(code)")
            } catch NewsError.decodingFailure(let error) {
                showError("数据解析失败:\(error.localizedDescription)")
            } catch NewsError.emptyData {
                showError("没有新闻数据")
            } catch {
                showError("未知错误:\(error)")
            }
        }
    }

    @MainActor
    private func showError(_ message: String) {
        errorLabel.text = message
        errorLabel.isHidden = false
        print("错误日志:\(message)") // 记录到日志
    }
}

// UITableViewDataSource 实现
extension NewsViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        newsItems.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "NewsCell", for: indexPath)
        let item = newsItems[indexPath.row]
        cell.textLabel?.text = item.title
        cell.detailTextLabel?.text = item.summary
        return cell
    }
}
  • @MainActor确保UI更新在主线程。
  • 分类型处理错误并显示用户友好提示。

运行结果

  • 成功时:newsItems填充数据,表格刷新显示新闻。
  • 失败时:根据错误类型显示对应提示,如“网络错误:The Internet connection appears to be offline”。

分析与改进

优势

  1. 集中错误处理:所有异常在一个catch块中管理。
  2. 用户体验:重试机制和错误提示提升健壮性。
  3. 可扩展性:易于添加新错误类型或调整逻辑。

可改进之处

  • 并行加载:若需要同时获取多源新闻,可用Task Group(后续章节介绍)。
  • 缓存机制:失败时加载本地缓存。
  • 进度反馈:显示加载动画。

示例改进(缓存回退):

func fetchNewsWithFallback() async -> [NewsItem] {
    do {
        let news = try await fetchNewsWithRetry()
        saveToCache(news) // 假设缓存函数
        return news
    } catch {
        print("获取失败,使用缓存:\(error)")
        return loadFromCache() // 返回缓存数据
    }
}

测试建议

  • 模拟网络中断:关闭Wi-Fi测试重试。
  • 无效URL:修改地址验证错误提示。
  • 空数据:返回空JSON测试解析。

小结

本节通过一个新闻应用的网络请求案例,展示了try/await在异步任务中的错误处理实践。从定义错误类型到实现重试和UI集成,我们构建了一个健壮的模块。这一案例整合了前两节的知识,体现了现代并发的实际价值。下一章将探讨异步序列与流,进一步扩展你在实时数据处理中的能力。


内容说明

  • 结构:从背景到实现步骤,再到分析和总结。
  • 代码:完整案例覆盖模型、请求、重试和UI,突出实战性。
  • 语气:实践性且指导性,适合技术书籍的核心章节。
  • 衔接:承接前两节(错误处理理论),预告后续(异步序列)。
Last Updated:: 3/4/25, 10:21 AM