Throw error from dataTask completionHandler - swift

In swift how do I throw an error within a completion handler like this:
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
do {
//something
completion(result)
} catch let jsonError {
throw CustomError.myerror //THIS DOESN'T WORK
}
})
task.resume()
as the error is
Invalid conversion from throwing function of type '(_, _, _) throws ->
()' to non-throwing function type '(Data?, URLResponse?, Error?) ->
Void'

Short story: You can't throw in a dataTask completion closure
You could return two values in the completion handler
...completion: #escaping (ResultType?, Error?)->Void
and return
completion(result, nil)
completion(nil, CustomError.myerror)
or more convenient use an enum with associated type
enum Result {
case success(ResultType), failure(Error)
}
...completion: #escaping (Result)->Void
and return
completion(.success(result))
completion(.failure(CustomError.myerror))
You can process the result
foo() { result in
switch result {
case .success(let resultType): // do something with the result
case .failure(let error): // Handle the error
}
}
Update:
In Swift 5 using the new built-in Result type it's even more comfortable because Result can capture the result of the throwing expression
...completion: #escaping (Result<MyType,Error>)->Void
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
completion(Result { try something()})
})
task.resume()
Update 2:
With async/await completion handlers are gone
do {
let (data, response) = try await URLSession.shared.data(for: request)
} catch {
throw CustomError.myerror
}

Related

Using flatMap in RestManager with generic function not working

I am new to Combine, so I wanted to create class RestManager for networking with generic
fetchData function. Function is returning AnyPublisher<Result<T, ErrorType>, Never> where ErrorType is enum with .noInternetConnection, .empty and .general cases.
I tried to use URLSession with dataTaskPublisher and flatMap
func fetchData<T: Decodable>(url: URL) -> AnyPublisher<Result<T, ErrorType>, Never> {
URLSession
.shared
.dataTaskPublisher(for: url)
.flatMap { (data, response) -> AnyPublisher<Result<T, ErrorType>, Never> in
switch response.result {
case .success(let data):
if let data = try? JSONDecoder().decode(T.self, from: data){
return Just(data).eraseToAnyPublisher()
}
case .failure(let error):
if let error = error as? URLError {
switch error.code {
case .notConnectedToInternet, .networkConnectionLost, .timedOut:
return Fail(ErrorType.noInternetConnection).eraseToAnyPublisher()
case .cannotDecodeRawData, .cannotDecodeContentData:
return Fail(ErrorType.empty).eraseToAnyPublisher()
default:
return Fail(ErrorType.general).eraseToAnyPublisher()
}
}
}
}
.eraseToAnyPublisher()
}
But I am getting
Cannot convert return expression of type 'AnyPublisher<AnyPublisher<Result<T, ErrorType>, Never>.Output, URLSession.DataTaskPublisher.Failure>' (aka 'AnyPublisher<AnyPublisher<Result<T, ErrorType>, Never>.Output, URLError>') to return type 'AnyPublisher<Result<T, ErrorType>, Never>' error.
There are several major flows in your implementation.
Firstly, you shouldn't be using Result as the Output type of the Publisher and Never as its Failure type. You should be using T as the Output and ErrorType as Failure.
Second, you need tryMap and mapError, not flatMap.
Lastly, you are handling the result of dataTaskPublisher completely wrong. When dataTaskPublisher fails, it emits an error, so you need to handle that in mapError. When it succeeds, it emits its result as data, so you need to be decoding that, not response.
func fetchData<T: Decodable>(url: URL) -> AnyPublisher<T, ErrorType> {
URLSession
.shared
.dataTaskPublisher(for: url)
.tryMap { data, _ in
return try JSONDecoder().decode(T.self, from: data)
}
.mapError { error -> ErrorType in
switch error {
case let urlError as URLError:
switch urlError.code {
case .notConnectedToInternet, .networkConnectionLost, .timedOut:
return .noInternetConnection
case .cannotDecodeRawData, .cannotDecodeContentData:
return .empty
default:
return .general
}
default:
return .general
}
}
.eraseToAnyPublisher()
}

Decoding to another class in do-try-catch produces compile error

I'm facing compile error.
I trying to decode API response into custom class(struct actually).
Our API returns several type of error (sometime it contain string but sometime it returns code (tuple [String: Int]))
So I would like to decode into another class if first decoding failed.
If I try decoding into just one class, it is okay but if I try to decode into another class in catch scope, it produces compile error
Invalid conversion from throwing function of type '(Data?, URLResponse?, Error?) throws -> Void' to non-throwing function type '(Data?, URLResponse?, Error?) -> Void'
Do you know how to solve this?
I'm useing Swift5, Xcode 12.4
struct ErrorInfo: Codable {
public let message: String?
public let details: String?
public let debugInformation: String?
}
struct ErrorInfoWithErrorCode: Codable {
public let message: [String: Int] // this is the difference.
public let details: String?
public let debugInformation: String?
}
let request = URLRequest(url: URL(string: "https://www.google.com/")!)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let httpResponse = response as? HTTPURLResponse, !(200..<300).contains(httpResponse.statusCode) {
do {
let errorMessage = try decoder.decode(ErrorInfo.self, from: data)
print(errorMessage)
} catch {
let errorMessage = try decoder.decode(ErrorInfoWithErrorCode.self, from: data)
print(errorMessage)
} catch {
fatalError()
}
}
}
task.resume()
Error message in playground.
error: MyPlayground.playground:28:54: error: invalid conversion from throwing function of type '(Data?, URLResponse?, Error?) throws -> Void' to non-throwing function type '(Data?, URLResponse?, Error?) -> Void'
let task = URLSession.shared.dataTask(with: request) { data, response, error in
The other nested try needs a do also , You can try
do {
let errorMessage = try decoder.decode(ErrorInfo.self, from: data)
print(errorMessage)
} catch {
do {
let errorMessage = try decoder.decode(ErrorInfoWithErrorCode.self, from: data)
print(errorMessage)
} catch {
fatalError()
}
}

Throwing errors from closure

I have this piece of code in my app:
func saveContact2(contact: String) throws {
let contactStore = CNContactStore()
contactStore.requestAccess(for: .contacts, completionHandler: {(granted, error) in
if granted && error == nil {
//...
} else {
if !granted {
throw contactErrors.contactAccessNotGranted(["Error","Access to Contacts is not granted."])
}
}
})
}
I'd like to throw all errors raising in closure to calling function.
Compiler shows error:
Invalid conversion from throwing function of type '(_, _) throws -> ()' to non-throwing function type '(Bool, Error?) -> Void'
Could anyone help me please with the right syntax?
You cannot throw errors from an #escaping closure that is called asynchronously. And this makes sense because your app has carried on with its execution and there’s no where to catch the error.
So, instead, adopt completion handler pattern yourself:
func saveContact2(_ contact: String, completion: #escaping: (Result<Bool, Error>) -> Void) {
let contactStore = CNContactStore()
contactStore.requestAccess(for: .contacts) { (granted, error) in
guard granted else {
completion(.failure(error!)
return
}
//...
completion(.success(true))
}
}
And then you’d call it like:
saveContact2(contactName) { result in
switch result {
case .failure:
// handler error here
case .success:
// handle confirmation of success here
}
}
If you’re using an old compiler that doesn’t have the Result type, it’s basically:
enum Result<Success, Failure> where Failure: Error {
case success(Success)
case failure(Failure)
}

Getting non--void error on function return [duplicate]

This question already has answers here:
Unexpected Non-Void Return Value In Void Function (Swift 2.0)
(2 answers)
Returning data from async call in Swift function
(13 answers)
Closed 4 years ago.
I'm getting this error and I know this problem has been addressed on here before by people not adding the return -> to the function. I do not understand why this is still giving me error.
Unexpected non-void return value in void function
I'm trying to return a String called message.
func ParseIt(proURL: String, startStr: String, stopStr: String) -> String {
let url = URL(string: "https://www.siteimfetchingfrom.com/827444000973")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error)
} else {
let htmlContent = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
//print(htmlContent)
// Get all Product Info
//var proName = "id=\"productName\" value=\""
if let contentArray = htmlContent?.components(separatedBy: startStr) {
//print(contentArray)
if contentArray.count > 0 {
//proName = "\" required"
let newContentArray = contentArray[1].components(separatedBy: stopStr)
if newContentArray.count > 0 {
let message = newContentArray[0]
//print(newContentArray)
print(newContentArray[0])
return message // Error happens Here
}
}
}
}
}
task.resume()
}
The line return message is written inside of a closure. A return statement written inside a closure will return from the closure, not the surrounding function.
Seeing how you are performing a web request and getting the response, you should have a completion handler instead of a return. You can't immediately return a string from ParseIt because the request will take time.
// notice the extra completion parameter and the removal of the return type
func ParseIt(proURL: String, startStr: String, stopStr: String, completion: #escaping (String) -> Void) {
let url = URL(string: "https://www.siteimfetchingfrom.com/827444000973")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
...
// replace the return statement with this:
completion(message)
}
task.resume()
}
You can call it like this:
ParseIt(proURL: ..., startStr: ..., stopStr: ...) {
result in
// do something with "result"
}
Look carefully where your return statement belongs. It's not returning from ParseInt, it's actually returning from the completion closure passed to URLSession.shared.dataTask. The return type of that completion handler is void.
func dataTask(with request: URLRequest, completionHandler: #escaping
(Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask

Custom Serialization finds incorrect "response" in Alamofire 4.0

I'm attempting to define a custom model serialization for Alamofire 4.0. So far I'm following the model presented used by responseJson and friends. Specifically, what I have so far is:
extension Alamofire.Request {
public static func serializeResponseModel<T:ModelObject>(response:HTTPURLResponse?, data:Data?, error:Error?) -> Alamofire.Result<T> {
switch serializeResponseJSON(options: [], response: response, data: data, error: error) {
case .success(let jsonObject):
do {
return .success(try T(json:jsonObject as! JSONObject))
}
catch {
return .failure(error)
}
case .failure(let error):
return .failure(error)
}
}
}
extension Alamofire.DataRequest {
public static func serializeResponseModel<T:ModelObject>() -> DataResponseSerializer<T> {
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponseConcierge(response: response, data: data, error: error)
}
}
#discardableResult
public func responseModel<T:ModelObject>(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<T>) -> Void) -> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.serializeResponseModel(),
completionHandler: completionHandler
)
}
}
Unfortunately, the framework is somewhat poorly implemented and the line return response( is finding the response property (defined in Request) and not the appropriate response method (defined in DataRequest), which leads to the compile error:
Cannot call value of non-function type 'HTTPURLResponse?'
What am I missing here that allows this to work in the responseJson case, but not in my case?
Apparently the problem arose from over-generalization, and the compiler not being able to generate an appropriate type for DataRequest.serializeResponseModel() When I changed responseModel to the following and specified the appropriate type, things work as expected:
#discardableResult
public func responseModel<T:ModelObject>(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<T>) -> Void) -> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.modelResponseSerializer() as DataResponseSerializer<T>,
completionHandler: completionHandler
)
}