3

Running into a strange error when using recover while handling errors that can be thrown while executing a promise.

Chaining .recover with .then results in compilation if there is more than one statement in recover block.

Having single statement in recover block works and having recover alone (promise.recover{} without then works)

Attaching screenshots of single statement recover (which works) and multiple statement recover (which throws a compilation error with the message: Ambiguous use of recover(on:__:)

Any help on how to debug this would be appreciated.

enter image description here enter image description here

Roman Podymov
  • 4,168
  • 4
  • 30
  • 57
user462455
  • 12,838
  • 18
  • 65
  • 96

2 Answers2

3

recover can return a Promise. If there is only 1 statement in your recover block then the compiler won't complain because there is only one line that could possibly return anything. When you add a second statement the compiler cannot infer which line returns something. Explicitly indicating that you are returning Void is one possible fix, assuming you don't intend to actually return anything.

func getNext() {
    taskGroup.getNext().then { data in
        self.initViewWithTask(data as! Task)
    }.recover { error -> Void in
        print("in recover")
        print("in recover 2")
    }
}

Explanation:

These four examples are functionally the same and will print One : Two : Three at the end. The closure in the first example is explicit in what parameter+type it will receive and what type be returned. Each subsequent example is less and less explicit about what is happening, so the complier must infer/deduce what is happening. The compiler can figure out what is happening because these examples are rather simple.

firstly {
    return Promise("One")
}.then { (result1: String) -> Promise<String> in
    return Promise("\(result1) : Two")
}.then { (result2: String) -> Promise<String> in
    return Promise("\(result2) : Three")
}.then { (result3: String) -> Void in
    print(result3)
    return
}

firstly {
    Promise("One")
}.then { (result1: String) -> Promise<String> in
    Promise("\(result1) : Two")
}.then { (result2: String) -> Promise<String> in
    Promise("\(result2) : Three")
}.then { (result3: String) -> Void in
    print(result3)
}

firstly {
    Promise("One")
}.then { result1 -> Promise<String> in
    Promise("\(result1) : Two")
}.then { result2 -> Promise<String> in
    Promise("\(result2) : Three")
}.then { result3 -> Void in
    print(result3)
}

firstly {
    Promise("One")
}.then { result1 in
    Promise("\(result1) : Two")
}.then { result2 in
    Promise("\(result2) : Three")
}.then { result3 in
    print(result3)
}

Now add a second line to the first closure. The compiler will be confused because it doesn't know what to return. It could be 999 or One. Even adding a simple print statement as you did will confuse the compiler and it will complain about ambiguous something. So either the compiler needs to get smarter (which it certainly could in the case of a simple print statement) or you need to be more explicit as to what is happening.

// ambiguous
firstly {
    Promise(999)
    Promise("One")
}.then { result1 in
    Promise("\(result1) : Two")
}.then { result2 in
    Promise("\(result2) : Three")
}.then { result3 in
    print(result3)
}

// clear
firstly {
    Promise(999)
    return Promise("One")
}.then { (result1: String) in
    Promise("\(result1) : Two")
}.then { result2 in
    Promise("\(result2) : Three")
}.then { result3 in
    print(result3)
}

As a side note... this really doesn't have anything to do with PromiseKit specifically. The examples could be written without PromiseKit. It is just understanding how the Swift complier interprets closures right now.

Jon
  • 3,208
  • 1
  • 19
  • 30
  • Thanks!! It worked. But still don't understand why compiler doesn't have any problems with one statement. What about second statement confuses the compiler. – user462455 Dec 28 '15 at 23:27
  • I'll try to provide an example later. You might check out this in the meantime http://code.tutsplus.com/tutorials/swift-from-scratch-closures--cms-23138. – Jon Dec 28 '15 at 23:36
  • Thanks for the detailed explanation!! Doesn't looking at the last statement hint compiler return type of the closure. Still confused why multiple statements confuses the compiler. Also is return statement implicitly added in swift closures. – user462455 Dec 29 '15 at 18:32
  • I don't think the compiler should assume that the last statement will be the return statement. That's just my opinion though. Whatever the reason... Apple docs for closures says: `Implicit returns from single-expression closures`. https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html – Jon Dec 29 '15 at 19:27
0

With PromiseKit 6.11.0 (Swift 5.1/Xcode 11.1) you should use done instead of then if you don't want to return Thenable from closure. And in such case, you will not get "Ambiguous use of recover":

taskGroup.getNextTask().done { data in
    self.initViewWithTask(data as! Task)
}.recover { error in
    NSLog("in recover")
}
Roman Podymov
  • 4,168
  • 4
  • 30
  • 57