Home > Enterprise >  Swift concurrency multiple await for single async result
Swift concurrency multiple await for single async result

Time:10-15

I have AuthorizationRequester which can be call from many places simultaneously, but only first call can run requestAuthorizationFromUser() (and wait dozen of seconds for user interaction) - rest of these calls should await for result from requestAuthorizationFromUser(), but can't call it directly.

Look at the code:

actor AuthorizationRequester {
    enum SimpleResult {
        case success, failure
    }
    
    typealias ResultCompletion = (SimpleResult) -> ()
    
    private var requestInProgress = false
    private var requestCompletions = [ResultCompletion]()
    
    func request(completion: @escaping ResultCompletion) async {
        requestCompletions.append(completion)
        
        guard !requestInProgress else { return }
        
        requestInProgress = true
        
        let result = await requestAuthorizationFromUser()
        
        requestCompletions.forEach { $0(result) }
        requestCompletions.removeAll()
        requestInProgress = false
    }
    
    private func requestAuthorizationFromUser() async -> SimpleResult {
        // ...some code
    }
}

Everything works, but I really don't like async combined with completion closure :)

Is there any possibility to rewrite this function to version with header func request() async -> SimpleResult and same functionality?

CodePudding user response:

You can save multiple calls to an async method await the same Task associated with the authorization request:

actor AuthorizationRequester {
    private var task: Task<SimpleResult, Never>?

    func request() async -> SimpleResult {
        if task == nil {
            task = Task { await requestAuthorizationFromUser() }
        }

        return await task!.value
    }

    private func requestAuthorizationFromUser() async -> SimpleResult {
        ...
    }
}

extension AuthorizationRequester {
    enum SimpleResult {
        case success, failure
    }
}

No closures required.

And if you want to reset it again when the Task finishes (like your sample effectively does), you can set task to nil when done:

func request() async -> SimpleResult {
    if task == nil {
        task = Task { await requestAuthorizationFromUser() }
    }

    let result = await task!.value
    task = nil
    return result
}
  • Related