Swiftylicious2
Swiftylicious2

Reputation: 21

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

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.

Upvotes: 2

Views: 186

Answers (3)

Aswath
Aswath

Reputation: 1480

I had this exact same problem and was in search of a solution. Rob's answer works in most cases and you should use it if async-await syntax is available on the private function. However in case you do not have access to the privateFunction as in case of OP, recursion is not avoidable(unless someone proves otherwise).

Since privateFunction needs a sync closure, just call myFunction inside a Task. This is how I made it work in playground

func privateFunction(range: ClosedRange<Int>, completion: @escaping ([String]) -> Void) {
    let items = (range.lowerBound...range.upperBound).filter { $0 < 100 }.map { "Item \($0)" }
    completion(items)
}

func myFunction(startIndex: Int = 0) async -> [String] {
    let maxSize = 10
    let endIndex = startIndex + maxSize - 1
    return await withCheckedContinuation { continuation in
        privateFunction(range: startIndex...endIndex) { items in
            if items.count < maxSize {
                continuation.resume(returning: items)
            } else {
                Task {
                    let nextList = await myFunction(startIndex: startIndex + maxSize)
                    continuation.resume(returning: items + nextList)
                }
            }
        }
    }
}

Task {
    await myFunction()
        .map { print($0) }
}

Upvotes: 0

Rob
Rob

Reputation: 437382

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
}

Upvotes: 2

Mr.SwiftOak
Mr.SwiftOak

Reputation: 1834

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

Upvotes: 1

Related Questions