Home > other >  Throwing an error from a stored throwing function
Throwing an error from a stored throwing function

Time:03-11

I have an array of throwing completion block handlers:

typealias CompletionBlock = (MyType) throws -> Void

private var handlers: [CompletionBlock] = []

I want to be able to recall them later on and throw an error like this:

for handler in handlers {
    handler(throw myError)
}

However, that doesn't work because it's expecting me to pass MyType to the handler, not an error. How can I do this?

CodePudding user response:

Your question is a bit vague, but I'm going to assume you mean that you want the handlers to accept the result of a throwing function. That's possible, and I'll show how, but it's not quite what you want.

"The result of a throwing function" can be expressed by calling a function that calls a throwing function:

typealias CompletionBlock = (() throws -> MyType) -> Void

Throwing an error in the way you've described like this:

let result: () throws -> MyType = { throw MyError() }

for handler in handlers {
    handler(result)
}

A handler would then look like:

func h(result: () throws -> MyType) {
    do {
        let myType = try result()
        // ...
    } catch {
        // ...
    }
}

But this is not a great design. Most importantly it executes result many times. Instead you want to pass the Result of a throwing function:

typealias CompletionBlock = (Result<MyType, Error>) -> Void

let f: () throws -> MyType = { throw MyError() }

let result = Result(catching: f)

for handler in handlers {
    handler(result)
}

The more common look for this is along these lines:

let result = Result {
    // Do various operations that might throw
    return value
}

You can also convert back from Result to a throws using try result.get(). So you can easily move between Result and throws wherever you like. Result is best for storing. Throws is best for calling. You can have both.

(And of course, you should also be looking to the future and exploring replacing completion handlers with async/await, but there are many reasons you might not do that right away, so Result will be an important tool for quite some time.)

CodePudding user response:

You are trying to change the behaviour of a stored closure by forcing it to throw an error regardless of its input. You cannot do that.

The fact that you marked CompletionBlock as throws only means that the closure itself might throw. However, you cannot force it to throw, since you can only pass an input argument of type MyType to it.

Moreover, there's no point in enforcing a closure to throw, since you need to handle the error thrown from the call-site anyways. So you can just simply throw the error from the call site.

Alternatively, if you need the closure itself to handle an error, you should change the closure to accept a Result and in case .failure in injected, the closure itself could throw.

typealias CompletionBlock = (Result<MyType, MyError>) throws -> Void

private var handlers: [CompletionBlock] = []

for handler in handlers {
    handler(.failure(myError))
}
  • Related