Combine 错误处理(二)

74 阅读2分钟

本篇文章介绍的是Combine开发过程中对错误的捕捉和处理行为,其也可以归类到常见运算符之中

catch

catch 用于捕获错误并提供一个替代的发布者。

import Combine

enum MyError: Error {
    case somethingWentWrong
}

let failPublisher = Fail<String, MyError>(error: MyError.somethingWentWrong)

let catchPublisher = failPublisher
    .catch { _ in
        return Just("Recovered from error")
    }

let cancellable = catchPublisher
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Completed")
        case .failure(let error):
            print("Error: (error)")
        }
    }, receiveValue: { value in
        print("Received value: (value)")
    })

// 输出:
// Received value: Recovered from error
// Completed

retry

retry 会在发布者失败时,重新尝试订阅。

import Combine

var attemptCount = 0

let retryPublisher = Deferred {
    return Future<String, MyError> { promise in
        attemptCount += 1
        if attemptCount < 3 {
            promise(.failure(MyError.somethingWentWrong))
        } else {
            promise(.success("Success after (attemptCount) attempts"))
        }
    }
}
.retry(3)

let cancellable = retryPublisher
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Completed")
        case .failure(let error):
            print("Error: (error)")
        }
    }, receiveValue: { value in
        print("Received value: (value)")
    })

// 输出:
// Received value: Success after 3 attempts
// Completed

tryMap

tryMap 类似于 map,但允许抛出错误。

import Combine

let numbers = [1, 2, 3, 4, 5]
let publisher = numbers.publisher

enum MyError: Error {
    case invalidNumber
}

let tryMapPublisher = publisher
    .tryMap { number -> Int in
        if number == 3 {
            throw MyError.invalidNumber
        }
        return number * 2
    }

let cancellable = tryMapPublisher
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Completed")
        case .failure(let error):
            print("Error: (error)")
        }
    }, receiveValue: { value in
        print("Received value: (value)")
    })

// 输出:
// Received value: 2
// Received value: 4
// Error: invalidNumber

mapError

mapError 用于将错误转换为另一种错误类型。

import Combine

enum OriginalError: Error {
    case somethingWentWrong
}

enum MappedError: Error {
    case mappedError
}

let failPublisher = Fail<String, OriginalError>(error: OriginalError.somethingWentWrong)

let mapErrorPublisher = failPublisher
    .mapError { _ in
        return MappedError.mappedError
    }

let cancellable = mapErrorPublisher
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Completed")
        case .failure(let error):
            print("Error: (error)")
        }
    }, receiveValue: { value in
        print("Received value: (value)")
    })

// 输出:
// Error: mappedError

replaceError

replaceError 是一种用于处理错误的操作符。它允许我们在遇到错误时,替换为一个默认值以确保数据流继续正常流动。它的作用是将错误替换为指定的输出值 output

假设我们有一个简单的网络请求示例,通过 URLSession 发起请求,并处理可能的错误情况。

import Combine
import Foundation

private var cancellables: [AnyCancellable] = []

enum DataError: Error {
    case invalidURL
    case networkError
    case parsingError
}

// 通过 URLSession.shared.dataTaskPublisher 发起一个网络请求,并尝试将接收到的数据转换为字符串。
func fetchDataFromURL(url: URL) -> AnyPublisher<String, DataError> {
    URLSession.shared.dataTaskPublisher(for: url)
        // 捕获可能的错误(例如网络错误或解析错误),并将其映射为 DataError 类型。
        .tryMap { data, _ in
            guard let string = String(data: data, encoding: .utf8) else {
                throw DataError.parsingError
            }
            return string
        }
        .mapError { error in
            if let urlError = error as? URLError {
                return .networkError
            } else {
                return .parsingError
            }
        }
        .eraseToAnyPublisher()
}

// 示例使用 replaceError 操作符
let url = URL(string: "https://www.example.com/data")!

fetchDataFromURL(url: url)
    // 用于在出现错误时,替换为默认的字符串 "Failed to load data"。
    .replaceError(with: "Failed to load data")
    // 订阅者用于接收和处理成功的值和错误,以及完成事件。
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Data fetching completed")
        case .failure(let error):
            print("Data fetching failed with error:", error)
        }
    }, receiveValue: { value in
        print("Received data:", value)
    })
    .store(in: &cancellables)

输出结果

  • 网络请求成功,输出:
Received data: Example Data
Data fetching completed
  • 网络错误或者解析错误,由于使用了 replaceError(with:) 操作符,输出将会是:
Received data: Failed to load data
Data fetching completed