Swift Optimise nested async calls

Viewed 234

I have this case where I need to make 3 nested async calls to receive the data I want. So the second call needs data from the first one and the third one needs data from the second one. I do not have a lot of cases like this. Only this one and another one with only two nested call so I was thinking about a pure swift solution without any external libraries but I'm open to everything.

Since I'm using Firebase, is it better to move this logic to CloudFunctions? So to prepare it in the backend?

FirestoreService().fetchCollection(query: query) { (result: Result<[Request], Error>) in
    // do stuff
    FirestoreService().fetchCollection(query: query) { (result: Result<[Request], Error>) in
        // do stuff
            FirestoreService().fetchDocument(documentReference: documentReference) { (result: Result<Package, Error>) in
                // finish
            }
        }

    }
}
1 Answers

If you don't to used 3rd party library, then probably you want to consider wrap those operations inside some class, and utilise closure in imperative way.

here is the sample:

class CustomFirestoreHandler {

    private var onFetchFirstQueryArrived: ((Result<[Request], Error>) -> ())? = nil
    private var onFetchSecondQueryArrived: ((Result<[Request], Error>) -> ())? = nil
    private var onFetchDocumentArrived: ((Result<Package, Error>) -> ())? = nil

    init() {

        onFetchFirstQueryArrived = { [weak self] (result: Result<[Request], Error>) in
            self?.executeSecondQuery()
        }

        onFetchSecondQueryArrived = { [weak self] (result: Result<[Request], Error>) in
            self?.executeFetchDocument()
        }
    }

    func executeQuery(completion: @escaping (Result<Package, Error>) -> ()) {

        self.onFetchDocumentArrived = completion

        FirestoreService().fetchCollection(query: query) { [weak self] (result: Result<[Request], Error>) in 

            // validate if some error occurred and do early return here, so that we don't need necessarily call second query.
            if (result.error == whatever) {
                 self?.onFetchDocumentArrived?(result)
                 return
            }

            self?.onFetchFirstQueryArrived?(result)
        }
    }

    private func executeSecondQuery() {
        FirestoreService().fetchCollection(query: query) { [weak self] (result: Result<[Request], Error>) in 

            // validate if some error occurred and do early return here, so that we don't need necessarily call fetch document.
            if (result.error == whatever) {
                 self?.onFetchDocumentArrived?(result)
                 return
            }

            self?.onFetchSecondQueryArrived?(result)
        }
    }

    private func executeFetchDocument() {
        FirestoreService().fetchDocument(documentReference: documentReference) { (result: Result<Package, Error>) in
            self?.onFetchDocumentArrived?(result)
        }
    }

}

And here's the usage of CustomFirestoreHandler above :

let firestoreHandler = CustomFirestoreHandler()
firestoreHandler.executeQuery { (result: Result<Package, Error>) in
    // Handle `result` here...
}

I know it look complicated, but this is the only way I think (CMIIW) at the moment to prevent pyramid of dooms since swift doesn't have async await style(just like javascript does).

Related