pankaj
pankaj

Reputation: 8388

Best way to call multiple API requests in a for loop in RxSwift

I have to make several api calls (approx 100) using a for loop and on completion of this I need to complete the Observable. I am using it as following:

func getMaterialInfo(materialNo:[String]) -> Observable<[String: Material]>{
    return Observable.create({ (observable) -> Disposable in
        for (index,mat) in materialNo.enumerated(){
            // Pass the material number one by one to get the Material object
            self.getMaterialInfo(materialNo: mat).subscribe(onNext: { material in
                var materialDict: [String: Material] = [:]
                materialDict[material.materialNumber] = material
                observable.onNext(materialDict)
                if index == (materialNo.count-1){
                    observable.onCompleted()
                }
            }, onError: { (error) in
                observable.onError(error)
            }, onCompleted: {
            }).disposed(by: self.disposeBag)
        }
        return Disposables.create()
    })
}

Although loop is working fine and observable.onCompleted() is called but the caller method does not receive it. I am calling it like following:

private func getImage(materialNo:[String]){
    if materialNo.isEmpty {
        return
    }
    var dictMaterials = [String:String]()
    materialService.getMaterialInfo(materialNo: materialNo).subscribe(onNext: { (materials) in
        for (key,value) in materials{
            if (value.imageUrl != nil){
                dictMaterials[key] = value.imageUrl
            }
        }
    }, onError: { (error) in

    }, onCompleted: {
        self.view?.updateToolImage(toolImageList: dictMaterials)
    }, onDisposed: {}).disposed(by: disposeBag)
}

OnCompleted block of Rx is not executing. How can I fix it?

Upvotes: 5

Views: 4046

Answers (1)

CloakedEddy
CloakedEddy

Reputation: 1995

Edit (5-March)

I revisited this answer, because I'm not sure what my brain was doing when I wrote the code sample below. I'd do something like this instead:

func getMaterialInfo(materialNo: String) -> Observable<[String: Material]> {
    // ...
}

func getMaterialInfo(materialNumbers:[String]) -> Observable<[String: Material]>{
        let allObservables = materialNumbers
            .map { getMaterialInfo(materialNo: $0) }

        return Observable.merge(allObservables)
}

Original answer

From your code, I interpret that all individual getMaterialInfo calls are done concurrently. Based on that, I would rewrite your getMaterialInfo(:[_]) method to use the .merge operator.

func getMaterialInfo(materialNo:[String]) -> Observable<[String: Material]>{
    return Observable.create({ (observable) -> Disposable in
        // a collection of observables that we haven't yet subscribed to
        let allObservables = materialNo
            .map { getMaterialInfo(materialNo: $0) }

        return Observable.merge(allObservables)
    }
    return Disposables.create()
}

Note that using merge subscribes to all observable simultaneously, triggering 100 network requests at the same time. For sequential subscription, use concat instead!

Upvotes: 1

Related Questions