Proper usage of RxSwift to chain requests, flatMap or something else?

前端 未结 1 1358
小蘑菇
小蘑菇 2021-02-01 09:24

First of all, I\'m new to rxswift so I guess the answer is obvious however at the moment I can\'t find solution by myself.

I have two functions:

func dow         


        
相关标签:
1条回答
  • 2021-02-01 10:15

    With RxSwift you want to use Observables whenever possible, therefore I recommend you to refactor the downloadAllTasks method to return an Observable<Task>. This should be fairly trivial by just looping through the elements instead of emitting the array directly:

    // In downloadAllTasks() -> Observable<Task>
    for task in receivedTasks {
        observable.onNext(task)
    }
    

    If this is not possible for whatever reason, there is also an operator for that in RxSwift:

    // Converts downloadAllTasks() -> Observable<[Task]> to Observable<Task>
    downloadAllTasks().flatMap{ Observable.from($0) }
    

    In the following code I will be using the refactored downloadAllTasks() -> Observable<Task> method because it's the cleaner approach.

    You can then map your tasks to get their id (assuming your Task type has the id: Int64 property) and flatMap with the downloadAllTasks function to get an Observable<TaskDetails>:

    let details : Observable<TaskDetails> = downloadAllTasks()
        .map{ $0.id }
        .flatMap(getTaskDetails)
    

    Then you can use the toArray() operator to gather the whole sequence and emit an event containing all elements in an array:

    let allDetails : Observable<[TaskDetails]> = details.toArray()
    

    In short, without type annotations and sharing the tasks (so you won't download them only once):

    let tasks = downloadAllTasks().share()
    
    let allDetails = tasks
        .map{ $0.id }
        .flatMap(getTaskDetails)
        .toArray()
    

    EDIT: Note that this Observable will error when any of the detail downloads encounters an error. I'm not exactly sure what's the best way to prevent this, but this does work:

    let allDetails = tasks
        .map{ $0.id }
        .flatMap{ id in
            getTaskDetails(id: id).catchError{ error in
                print("Error downloading task \(id)")
                return .empty()
            }
        }
        .toArray()
    

    EDIT2: It's not gonna work if your getTaskDetails returns an observable that never completes. Here is a simple reference implementation of getTaskDetails (with String instead of TaskDetails), using JSONPlaceholder:

    func getTaskDetails(id: Int64) -> Observable<String> {
        let url = URL(string: "https://jsonplaceholder.typicode.com/posts/\(id)")!
        return Observable.create{ observer in
            let task = URLSession.shared.dataTask(with: url) { data, response, error in
                if let error = error {
                    observer.onError(error)
                } else if let data = data, let result = String(data: data, encoding: .utf8) {
                    observer.onNext(result)
                    observer.onCompleted()
                } else {
                    observer.onError("Couldn't get data")
                }
            }
            task.resume()
    
            return Disposables.create{
                task.cancel()
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题