如何在SwiftUI中使用async/await链接AnyPublisher?

4

将 AnyPublisher 进行链式操作的典型方式是使用 Combine 操作符,例如 flatMap。


class MyService {
    func getUserList() -> AnyPublisher<[User], Error> {
        ....
    }

    func getPostList(user: User) -> AnyPublisher<[Post], Error> {
        ...
    }
}

class ViewModel: ObserableObject {
    let service = MyService()
    
    @published var post: [Post] = []
    
    func fetchAllPostFromLastUser() {
        service.getUserList().flatMap { [weak self] users in
            if let user = users.last {
                return self.service.getPosts(user: user)
            } else {
                return Fail(error:APIError.emptyUsers).eraseToAnyPublisher()
            }
        }
        .sink { result in
            
        }
    }
}

有没有更优雅的方式使用async/await,使得代码可以像下面这样简洁

class ViewModel: ObserableObject {
    let service = MyService()
    
    @published var post: [Post] = []
    
    func fetchAllPostFromLastUser() {
        let users = await service.getUserList().somethingMagicToConvertPublisherToAsync()
        let posts = await service.getPostList(user: user.first).somethingMagicToConvertPublisherToAsync()
    }
}


观看来自WWDC21的async await Meet。 - lorem ipsum
2个回答

1
为了使用async/await,我们需要将Publisher转换为值的AsyncStream,以消除错误。

因此,可能是这样的(在Xcode 13.4中测试)

@MainActor class ViewModel: ObserableObject {
    // ...

    func collectPosts() async {
        for await newUsers in service.getUserList().replaceError(with: []).values {
            for user in newUsers {
                for await newPost in service.getPost(user: user).replaceError(with: []).values {
                    post.append(contentsOf: newPost)
                }
            }
        }
    }

以及使用方式如下:

struct ContentView: View {
    @StateObject var vm = ViewModel()

    var body: some View {
        YourViewHere()
            .task {
                await vm.collectPosts()
            }
    }
}

1

添加 AnyPublisher 扩展:

enum AsyncError: Error {
    case finishedWithoutValue
}

extension AnyPublisher {
    func async() async throws -> Output {
        try await withCheckedThrowingContinuation { continuation in
            var cancellable: AnyCancellable?
            var finishedWithoutValue = true
            cancellable = first()
                .sink { result in
                    switch result {
                    case .finished:
                        if finishedWithoutValue {
                            continuation.resume(throwing: AsyncError.finishedWithoutValue)
                        }
                    case let .failure(error):
                        continuation.resume(throwing: error)
                    }
                    cancellable?.cancel()
                } receiveValue: { value in
                    finishedWithoutValue = false
                    continuation.resume(with: .success(value))
                }
        }
    }
}

并这样使用

let todo = try await api.loadTodo().async()

更多信息在这里 https://medium.com/geekculture/from-combine-to-async-await-c08bf1d15b77


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接