swiftnsurlsessiondatatask

Throw error from dataTask completionHandler


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'


Solution

  • 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
    }