concurrencycore-locationswift5promisekit

Pending Task in Swift


Now I am migrating from PromiseKit to Concurrency. As I understood, I should replace Promise and Guarantee with Task. However, I cannot find a replacement for Promise<T>.pending(). Is there something similar in Concurrency?

For instance, I want to use Task in the code below:

import CoreLocation
import PromiseKit

class A: NSObject {
    let locationManager = CLLocationManager()
    let promiseAndResolver = Promise<CLLocation>.pending()
    
    func f() {
        locationManager.delegate = self
        locationManager.requestLocation()
    }
}

extension A: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.first {
            promiseAndResolver.resolver.fulfill(location)
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        promiseAndResolver.resolver.reject(error)
    }
}

class B {
    let a = A()
    
    func f() {
        a.promiseAndResolver.promise.done {
            print($0)
        }.cauterize()
        a.f()
    }
}

let b = B()
b.f()

Solution

  • You don't need to use Task to do this. You can use withCheckedThrowingContinuation to convert the method f into an async throws method. Then when you call it, a child task of the current task is automatically created and you can await it directly.

    class A: NSObject, CLLocationManagerDelegate {
        let locationManager = CLLocationManager()
        var continuation: CheckedContinuation<CLLocation, Error>?
        func f() async throws -> CLLocation {
            try await withCheckedThrowingContinuation { continuation in
                self.continuation = continuation
                locationManager.delegate = self
                locationManager.requestLocation()
            }
        }
    
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            continuation?.resume(returning: locations[0])
        }
        
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            continuation?.resume(throwing: error)
        }
    }
    

    Your B.f method can also be rewritten as an async method (I don't know what cauterize does):

    class B {
        let a = A()
        
        func f() async {
            do {
                print(try await a.f())
            } catch { 
                print(error) 
            }
        }
    }
    
    let b = B()
    
    _runAsyncMain { // so that we have an async context to await tasks
        await b.f()
    }
    

    Side note: if you actually run this, you will get an error printed from the catch block, probably because location manager needs permission :)