14

I'd like to use consecutive try statements. If one returns an error I'd like to proceed to the next one, otherwise return the value. The code below seems to work fine, however I'll end up with a big nested do catch pyramid. Is there a smarter/better way to do it in Swift 3.0?

do {
    return try firstThing()
} catch {
    do {
        return try secondThing()
    } catch {
        return try thirdThing()
    }
}
Mymodo
  • 163
  • 1
  • 7
  • 3
    do all your `try` statements inside your `do` and catch any exception in your `catch`. No need to have them nested at all. – Pancho Aug 14 '17 at 10:35
  • 3
    This won't work @Pancho if the OP only wants to run second thing if firstThing fails and run thirdThing if secondThing fails. – Abizern Aug 14 '17 at 10:38
  • Thanks @Pancho, but since I'm returning the value(or error) any code the first return won't be executed. – Mymodo Aug 14 '17 at 10:40
  • @Abizern this is true. In such case the do-catch have to be replaced by if-else or switch statement which is not going to reduce the code or make it pretty. – Pancho Aug 14 '17 at 10:46
  • 5
    Why the downvotes? – Nikolai Ruhe Aug 14 '17 at 10:53

3 Answers3

17

If the actual errors thrown from those function calls are not needed then you can use try? to convert the result to an optional, and chain the calls with the nil-coalescing operator ??.

For example:

if let result = (try? firstThing()) ?? (try? secondThing()) ?? (try? thirdThing()) {
    return result
} else {
    // everything failed ...
}

Or, if the error from the last method should be thrown if everything fails, use try? for all but the last method call:

return (try? firstThing()) ?? (try? secondThing()) ?? (try thirdThing())
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
17

If Martin's answer is too terse for your taste you can just go with individual catch blocks.

do {
    return try firstThing()
} catch {}

do {
    return try secondThing()
} catch {}

do {
    return try thirdThing()
} catch {}

return defaultThing()

As each throwing function's result is immediately returned no nesting is necessary.

Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200
4

Another way to do this is to write a function that takes all your throwing functions as an argument. It returns the first one that was successfully executed or nil.

func first<T>(_ values: (() throws -> T)...) -> T? {
    return values.lazy.flatMap({ (throwingFunc) -> T? in
        return try? throwingFunc()
    }).first
}

The lazy ensures that the values are only called until it finds the first match. Doing it this way, you can also add a lot of cases very quickly.

You can use the function like this

return first(firstThing, secondThing, thirdThing) ?? "Default"

I also included the code I used to test this in playground:

enum ThingError: Error {
    case zero
}

func firstThing() throws -> String {
    print("0")
    throw ThingError.zero
    return "0"
}

func secondThing() throws -> String {
    print("1")
    return "1"
}

func thirdThing() throws -> String {
    print("2")
    return "B"
}

func first<T>(_ values: (() throws -> T)...) -> T? {
    return values.lazy.flatMap({ (throwingFunc) -> T? in
        return try? throwingFunc()
    }).first
}

func tryThings() -> String {
    return first(firstThing, secondThing, thirdThing) ?? "Default"
}

tryThings() // prints "0" and "1"
Yannick
  • 3,210
  • 1
  • 21
  • 30