Any other solution to use Promise or Future like async/await in iOS project?

Note: My project needs to run at OS version before iOS 13, so I can not use async/await feature.

In JS world a Promise is awaitable, so I want to use Promise in the same way, library like Google's promises have an await API promises/index.md at master · google/promises (github.com)

but it uses GCD semaphore to block the current thread, the current thread mostly is the main thread, docs of promises library is recommended to use await in a background thread, it can work, but will cause other issues, like UI refresh mostly needed after network request end, with this solution, developers will need to dispatch to main thread manually, so I think this solution is hard to use.

swift-nio library offers a Future and Promise implement, but it needs to maintain the eventloop by the developer, seems it's not a good idea.

So does there have some solution to make Promise can be await at the main thread? like Swift 5.5 await feature.

There are lots of Swift promise libraries out there. A search on the Swift Package Index turns up several. Try a few and see which one you like best. I've used PromiseKit in the past due to its Obj-C compatibility, but there are many others.

This is not a library choose issue, I have tried google's promises, Hydra, PromiseKit etc, none of them can be await on the main thread,

All of them use a semaphore to block the current thread, so they can not be await at the main thread.

I'm quite sure that's not true, as we use PromiseKit on the main thread all the time. I suggest you actually try the libraries rather than just theorizing about them.

No, I think we have different ways of using these libraries, with google's promises library, this code will cause a deadlock, code should run at main thread.

        let promise = Promise<String>(on: .global()) { fulfill, reject in
            // Called asynchronously on the dispatch queue specified.
            let queue = DispatchQueue.main
//            let queue = DispatchQueue.global()
            queue.asyncAfter(deadline: .now() + 1) {
                fulfill("Hello")
            }
        }
        try? promise.await()
        print("ahh")

Absolutly this code can run at main thread, but they have different patern.

        let promise = Promise<String>(on: .global()) { fulfill, reject in
            // Called asynchronously on the dispatch queue specified.
            let queue = DispatchQueue.main
//            let queue = DispatchQueue.global()
            queue.asyncAfter(deadline: .now() + 1) {
                fulfill("Hello")
            }
        }
        promise.then { s in
            print(s)
        }
        print("ahh")
1 Like

Ekko is specifically talking about using await() to get the promise's resolution.

@EkkoG

There is no way to do what you're asking. The inability to do so is a small part of why concurrency was added to Swift in the first place. For platforms which are unsupported by the new concurrency features, you do not have compiler support to make this possible.

I implemented my own promise based library in the past (just to see what's involved, btw it is not so complicated and by doing this exercise you can make it tailored to your needs, e.g. you can make it completely free of semaphores or other blocking constructs) and came to a firm conclusion that a non blocking call like:

func viewDidLoad() {
    super.viewDidLoad()
    let result = await foo() // pseudo code, syntax depends upon particular async/await library
    // do something with the result
    textView.text = result
}

done on main thread is impossible (Edit: see a message from me 4 messages below for a workaround). The closest would be something like:

func viewDidLoad() {
    super.viewDidLoad()
    foo().await { [self] result in
        // do something with the result
        textView.text = result
    }
}

Can this work for you?

As a side note, in my view the horrors of callback "hell" are exaggerated in case of iOS applications. The callback depths are not so great and once they start getting out of hand it is easy to split functionality by methods, which also helps to keep methods short (the 7 line rule) addressing both depth and length issues.

The question also makes me wonder how many iOS users (in percents or absolute numbers) use iOS 12 and earlier.

This is another form of callback.

await is not only solved the callback hell issue but also can make code easier to understand.

Some platforms like E-commerce and train ticket service, have some duty to make as much as possible can use their service, so they need support some older system, even if these users are not many.

Yes, this is a callback, but until Swift adopted the async-await syntax there was no way to do non-blocking code flow. The various promise libraries allowed users to eliminate callback hell by chaining work together rather than nesting it so there's only a single level of callback to deal with.

Those are your two options: adopt async-await and require iOS 13+ or find your own concurrency solution (promises, RxSwift, whatever) if you need to support older OSes.

As long as these services provide a website their mobile apps can support the latest and greatest, IMO. Mobile apps are a luxury which provide special capabilities, otherwise they're just more expensive websites.

Many users in China have no idea what a website is and what url is, it's a pity, but it is true.

For just now, I see the biggest app in China support iOS 12 and later, iOS 12 is much high as a minimum support version I think, since the app is Wechat, maybe I can recommend my company to support iOS 13 and later.

To expand on this a bit further, yes, if you want so you can have this looking code:

func viewDidLoad() {
    print("start of viewDidLoad")
    super.viewDidLoad()
    let asyncImage = foo() // non blocking call
    let asyncString = bar() // non blocking call
    imageView.asyncImage = asyncImage // non blocking call
    textView.asyncString = asyncString // non blocking call
    print("end of viewDidLoad")
}

Where callback machinery is hidden inside asyncImage (asyncString, etc) setter:

extension UIImage {
    var asyncImage: Async<UIImage> {
        get { ... }
        set {
            newValue.await { [self] result in
                print("asyncImage set")
                image = result
            }
        }
    }
}

output:

start of viewDidLoad
end of viewDidLoad
... some time later
asyncString set
... some time later
asyncImage set

Then go for it. It opens you the whole new SwiftUI world. Also note that any device that supports iOS 13 also supports iOS 14 and iOS 15!

Terms of Service

Privacy Policy

Cookie Policy