How would I convert a completion hander function to async when recursion is involved?

Viewed 77

Currently I call myFunction as such:

myFunction() { list in
    // do whatever with list
}

I would like to be able to call it like this:

let list = await myFunction()
// do whatever with list

So from what I understand I need to convert myFunction to be async. Currently it looks like:

func myFunction(currentList: [String] = [], startIndex: Int = 0, completion: @escaping ([String]) -> Void) {
    var list = currentList
    let maxSize = 10
    let endIndex = start + maxSize - 1
    privateFunction(range: startIndex...endIndex) { items in
        list.append(items)
        if items.count < maxSize {
            completion(list)
        } else {
            myFunction(currentList: list, startIndex: startIndex + maxSize, completion: completion)
        }
    }
}

However, myFunction calls a private function that I do not have access to so cannot modify. I learned that a way to deal with this is to use withCheckedContinuation. So I modify myFunction to look like this:

func myFunction(currentList: [String] = [], startIndex: Int = 0) async -> [String] {
    var list = currentList
    let maxSize = 10
    let endIndex = start + maxSize - 1
    return await withCheckedContinuation { continuation in
        privateFunction(range: startIndex...endIndex) { items in
            list.append(items)
            if items.count < maxSize {
                continuation.resume(returning: list)
            } else {
                // ???
            }
        }
    }
}

I'm not sure what to put in the commented area above, since when that part of the code is reached, the function is to call itself unless the condition is met.

2 Answers

With async-await, the recursion is no longer necessary.

First, you would make an asynchronous rendition of your private function, e.g.

func privateFunction(range: ClosedRange<Int>) async -> [String] {
    await withCheckedContinuation { continuation in
        privateFunction(range: range) { items in
            continuation.resume(returning: items)
        }
    }
}

And then, myFunction is reduced to a simple loop:

func myFunction() async -> [String] {
    var results: [String] = []
    var items: [String]
    let maxSize = 10
    var startIndex = 0
    
    repeat {
        let endIndex = startIndex + maxSize - 1
        items = await privateFunction(range: startIndex...endIndex)
        results += items
        startIndex += maxSize
    } while items.count >= maxSize 
    
    return results
}

Try using this async implementiation of myFuntion :

    @available(iOS 15.0,tvOS 15.0, macOS 12.0, *)
public func myFunction(currentList: [String] = [], startIndex: Int = 0) async throws -> [String] {
    return try await withCheckedThrowingContinuation { continuation in
         self.myFunction(currentList: currentList, startIndex: startIndex) { result in
            continuation.resume(returning: result)
        }
    }
}
Related