How to chain AnyPublisher with async/await in SwiftUI

Viewed 276

A typical way to chain AnyPublisher is to use Combine operators like 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
            
        }
    }
}

Is there a more elegant way to use async/await, so the code can be similar like

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()
    }
}

1 Answers

To use async/await we need to convert Publisher into AsyncStream of values, so get rid of errors.

So, it is possible to be like (tested with 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)
                }
            }
        }
    }

and usage like

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

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