Matteo Manni
Matteo Manni

Reputation: 50

PromiseKit: delegate-system wrappers seem to return immediately when not used at the beginning of the chain

I am fairly new to PromiseKit and I have been trying for a couple of days to figure out a solution for an unexpected behaviour of promise-wrapped delegate systems (UIALertView+PromiseKit, PMKLocationManager etc..).

In my fairly typical scenario of an app's Setup process, I am trying to chain up a series of operations that the user has to go through when the app loads. For the sake of this example, let's restrict the case to only two steps: logging the user into a Restful system followed by presenting an alertView and waiting for user's interaction.

Below is my code, where:

  1. LoginToService is a promise-able version of a block-based method obtained via extending MCUser with PromiseKit. This works as expected and returns once the user had logged in, or fails with an error.

  2. In the 'then' clause of the successful login, I present an alertView by returning its promised version via alert.promise().

    I would expect that promise to be fulfilled before the successive .then clause (and in the end the 'finally' clause) gets called - the alert's promise should be fulfilled when the the user clicks on button to dismiss it, as per implementation of PromiseKit's delegate system wrappers: this works just fine the observed behaviour when I use alert.promise().then to start the chain of Promises -

        // Doesn't work: alert.promise returns immediately 
    let user = MCUser.sharedInstance()
    user.logInToService(.RestServiceOne, delegate: self).then { _ -> AnyPromise in
        MCLogInfo("LogInToService: Promise fulfilled")
        let alert = UIAlertView(title: "Title", message: "Msg", delegate: nil, cancelButtonTitle: "Cancel", otherButtonTitles: "Hello")
        return alert.promise()
        }.then { (obj:AnyObject) -> Void in
            print("Clicked")
        }.finally {
            print("Finally")
        }.catch_ { error in
     print("Error")
    }
    

What I observe is that the chain continues immediately without waiting for the user to click, with the 'Clicked' and 'Finally' messages being printed to console and the alert on screen waiting for an action. Am I obviously missing something or ar those delegate-system wrappers not meant to be used if not at the beginning of the Promise chain?

Thanks in advance for any hint

Upvotes: 2

Views: 482

Answers (1)

CouchDeveloper
CouchDeveloper

Reputation: 19134

It should work as you expect. You may check whether the returned promise is incorrectly completed.

What makes me gripes, though, is that alert.promise() should return a Promise<Int> - but the closure is explicitly typed to return a AnyPromise. So, your code should not compile.

I setup a test project myself, and indeed, the compiler complains. I used PromiseKit v3.x. Your's is likely an older version (finally and catch are deprecated).

After fixing the return type of the closure to Promise<Int>, the code compiles. But the important fact is, that the behaviour is as you described and experienced in your code - and not as it should, IMHO. So, there seems to be a bug.

Edit:

OK, it turned out that there is an issue with "overload resolution" and "type inference". Given your code in your OP, the Swift compiler resolves to an unexpected overload of the then method:

expected:
func then<U>(on q: dispatch_queue_t = dispatch_get_main_queue(), _ body: (T) throws -> Promise<U>) -> Promise<U>

actual:
func then<U>(on q: dispatch_queue_t = dispatch_get_main_queue(), _ body: (T) throws -> U) -> Promise<U>

This is caused by the succeeding finally and catch methods.

In order to solve it here in this scenario, you should either correctly fully specify the type of the closure, or let the compiler figure it out themselves by not specifying the type. I finally got this, which works as expected using PromiseKit v3.x with Swift:

import UIKit
import PromiseKit

// helper
func fooAsync() -> Promise<String> {
    return Promise { fulfill, reject in
        let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(1.0 * Double(NSEC_PER_SEC)))
        dispatch_after(delay, dispatch_get_global_queue(0,0)) {
            fulfill("OK")
        }
    }
}        


class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()            
        fooAsync()
        .then { str in
            print("String: \(str)")
            let alert = UIAlertView(title: "Title", message: "Msg", delegate: nil, cancelButtonTitle: "Cancel", otherButtonTitles: "Hello")
            let promise: Promise<Int> = alert.promise()
            return promise
        }.then { (n: Int) -> Void in   // notice: closure type specified!
            print("Clicked")
        }.ensure {
            print("Finally")
        }.report { error in
            print("Error")
        }

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

This above code may not solve it in your case, since you are using a different PromiseKit library. I would recommend to use the newest Swift version.

Nonetheless, there seem to be some subtle pitfalls here with PromiseKit. Hope you can now solve your issue.

Upvotes: 1

Related Questions