I have a completion handler that gets called inside my closure. However, the completion handler only gets called when everything goes well. In the case of an error, the completion handler never gets called.
func product(with id: String, _ completion: @escaping (Product) -> ()) {
// Make a network request for the product
...
if (product) {
completion(product)
}
}
Is this a bad design? I recently got the comment that completion handlers need to be called even in case of errors, otherwise the caller will be waiting indefinitely. I've never heard that before and now I'm wondering if this applies to Swift.
Strictly spoken the caller doesn't wait at all. The code in the closure will or will not be executed.
However it's good practice to return errors, too.
A smart way is the Result type
func product(with id: String, completion: @escaping (Result<Product,Error>) -> Void) {
// Make a network request for the product
...
if error = error { completion(.failure(error)); return }
if product {
completion(.success(product))
} else {
let error = // create some other error
completion(.failure(error))
}
}
And call it
product(with: "Foo") { result in
switch result {
case .success(let product): // do something with the product
case .failure(let error): // do something with the error
}
}
Note: The underscore character before completion in the function declaration is pointless.
If you will not call a completion nothing will happen 'cuz completion caller will not wait for it.
But if you want to cover all cases try to add a failure callback. For example:
func product(with id: String, _ success: @escaping (Product) -> (), failure: @escaping (Any) -> ())
In your case, if you are treating it as a completion, it means that it has to get called no matter what's case (success of failure with error), it has to returns when the process completed.
What you could do is to pass an optional error and product to the completion closure and then check whether the error is nil or not:
func product(with id: String, _ completion: @escaping (Product?, Error?) -> ()) {
// in case of there is an error:
completion(nil, error)
return
// if things went happy:
completion(product, nil)
}
Calling the method:
product(with: "ID") { (product, error) in
guard let returnedError = error else {
print(product)
return
}
print(returnedError)
}
Or:
product(with: "ID") { (product, error) in
if let returnedError = error {
print(returnedError)
return
}
print(product)
}