在 Combine 中链接 n 个请求

Chaining n requests in Combine

我正在尝试将 n 请求与 Combine 链接起来。

假设我有 50 个用户,我需要为每个用户执行一个请求来获取用户数据。我知道使用 flatMap 您可以将一个 Publisher 结果传递给下一个。但这也适用于循环吗?

这是我获取用户的函数:

func fetchUser(for id: Int) -> AnyPublisher<User, Error> {
    let url = "https://user.com/api/user/\(id)"
    return URLSession.shared.dataTaskPublisher(for: url)
        .mapError { [=10=] as Error }
        .map { [=10=].data }
        .decode(type: User.self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

所以基本上我需要另一个函数,它循环这个 fetchUser 和 returns 一个结果数组中的所有用户。这些请求不应该同时 运行,而是在前一个完成后开始一个。

对于这一项,很大程度上取决于您希望如何使用用户对象。如果您希望它们在进入时都作为单个用户发出,那么 merge 就是解决方案。如果您想保持数组顺序并在用户全部进入后将它们全部作为用户数组发出,那么 combineLatest 就是您所需要的。

由于您处理的是数组,merge 和 combineLatest 都没有数组版本,因此您需要使用 reduce。以下是示例:

func combine(ids: [Int]) -> AnyPublisher<[User], Error> {
    ids.reduce(Optional<AnyPublisher<[User], Error>>.none) { state, id in
        guard let state = state else { return fetchUser(for: id).map { [[=10=]] }.eraseToAnyPublisher() }
        return state.combineLatest(fetchUser(for: id))
            .map { [=10=].0 + [[=10=].1] }
            .eraseToAnyPublisher()
    }
    ?? Just([]).setFailureType(to: Error.self).eraseToAnyPublisher()
}

func merge(ids: [Int]) -> AnyPublisher<User, Error> {
    ids.reduce(Optional<AnyPublisher<User, Error>>.none) { state, id in
        guard let state = state else { return fetchUser(for: id).eraseToAnyPublisher() }
        return state.merge(with: fetchUser(for: id))
            .eraseToAnyPublisher()
    }
    ?? Empty().eraseToAnyPublisher()
}

请注意,在合并的情况下,如果数组为空,发布者将发出一个空数组然后完成。在合并的情况下,它会完成而不会发出任何东西。

另请注意,在任何一种情况下,如果任何发布者失败,则整个链将关闭。如果你不想那样,你将不得不捕获错误并对它们做一些事情......