Swift的do/try/catch – 在catch块中访问API响应

huangapple go评论84阅读模式
英文:

Swift do/try/catch - accessing API response in catch block

问题

Taskcatch块中,您可以访问LoginResponse中的"msg"如下:

} catch {
    if let loginError = error as? LoginError {
        let errorMessage = loginError.msg
        // 处理错误消息
    } else {
        // 处理其他错误
    }
}

为了实现这一点,您需要创建一个自定义错误类型LoginError,该类型包含与LoginResponse中的错误消息相对应的属性。然后,在发生错误时,您可以检查错误类型并访问相关的错误消息。

如果要重新组织do/catch块以便在catch块中访问LoginResponse对象,您可以将LoginResponse的声明移到do块的范围内,以便在catch块中也能访问它,如下所示:

Task {
    do {
        let request: Request<LoginResponse> = .postLogin(email: email, password: password)
        let response = try await URLSession.shared.decode(request)
        // store token if received
    } catch {
        if let loginError = error as? LoginError {
            let errorMessage = loginError.msg
            // 处理错误消息
        } else {
            // 处理其他错误
        }
    }
}

struct LoginResponse: Decodable {
    let token: String
    let msg: String
}

这样,您可以在catch块中直接访问LoginResponse对象,以及其中的"msg"属性。

英文:

Say I'm making an API call like this:

Task {
    do {
        let request: Request<LoginResponse> = .postLogin(email: email, password: password)
        let response = try await URLSession.shared.decode(request)
        // store token if received
    } catch {
        // ???
    }
}

struct LoginResponse: Decodable {
    let token: String
    let msg: String
}

extension URLSession {
    func decode<Value: Decodable>(
        _ request: Request<Value>,
        using decoder: JSONDecoder = .init()
    ) async throws -> Value {
        let decoded = Task.detached(priority: .userInitiated) {
            let (data, _) = try await self.data(for: request.urlRequest)
            try Task.checkCancellation()
            return try decoder.decode(Value.self, from: data)
        }
        return try await decoded.value
    }
}

In the catch block of the Task, how would I access the "msg" from LoginResponse? It's an error message being returned from the backend that the login info is incorrect. Or how would I restructure the do/catch so that I can access the LoginResponse object in the catch block?

For further info on the Request object, see here

答案1

得分: 1

如果您的后端在登录失败时仍然返回HTTP响应,那么try await self.data(for: request.urlRequest)不会引发错误。它将返回一个HTTPURLResponse(您完全忽略了它的_部分),其中包含指示错误的statusCode

self.data(for: request.urlRequest)仅在没有响应时引发异常,例如当您使用无效的URL或没有互联网连接时。

因此,您可以将HTTPURLResponse返回给调用者,并在do块中进行检查:

func decode<Value: Decodable>(
    _ request: Request<Value>,
    using decoder: JSONDecoder = .init()
) async throws -> (Value, HTTPUURLResponse?) {
    let decoded = Task.detached(priority: .userInitiated) {
        let (data, response) = try await self.data(for: request.urlRequest)
        try Task.checkCancellation()
        return try (
            decoder.decode(Value.self, from: data),
            response as? HTTPURLResponse // 如果请求是HTTP请求,此强制转换将成功
        )
    }
    return try await decoded.value
}
let request: Request<LoginResponse> = .postLogin(email: email, password: password)
let (decodedData, response) = try await URLSession.shared.decode(request)
if let httpResponse = response, httpResponse.statusCode >= 400 {
    // 处理带有decodedData的错误...
}

如果您希望在catch块中处理它,您可以在任务中进行检查并引发错误。

func decode<Value: Decodable, Failure: Error & Decodable>(
    _ request: Request<Value>,
    using decoder: JSONDecoder = .init(),
    errorResponseType: Failure.Type
) async throws -> Value {
    let decoded = Task.detached(priority: .userInitiated) {
        let (data, response) = try await self.data(for: request.urlRequest)
        try Task.checkCancellation()
        if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode >= 400 {
            throw try decoder.decode(errorResponseType, from: data)
        }
        return try decoder.decode(Value.self, from: data)
    }
    return try await decoded.value
}
// 这些可以是相同的类型,但我更喜欢将错误与成功响应分开
struct LoginResponse: Decodable {
    let token: String
    let msg: String
}

struct LoginError: Decodable, Error {
    let token: String
    let msg: String
}
do {
    let request: Request<LoginResponse> = .postLogin(email: email, password: password)
    let decodedData = try await URLSession.shared.decode(request, errorResponseType: LoginError.self)
} catch let error as LoginError {
    // 处理登录错误...
} catch {
    // 处理其他错误...
}
英文:

If your backend still gives you an HTTP response if the login failed, then try await self.data(for: request.urlRequest) won't throw an error. It will return a HTTPURLResponse (which you completely ignored with _) with a statusCode indicating an error.

self.data(for: request.urlRequest) would only throw when there is no response at all, like when you used an invalid URL, or there is no internet connection.

Therefore, you can return the HTTPURLResponse back to the caller, and check it in the do block:

func decode&lt;Value: Decodable&gt;(
    _ request: Request&lt;Value&gt;,
    using decoder: JSONDecoder = .init()
) async throws -&gt; (Value, HTTPUURLResponse?) {
    let decoded = Task.detached(priority: .userInitiated) {
        let (data, response) = try await self.data(for: request.urlRequest)
        try Task.checkCancellation()
        return try (
            decoder.decode(Value.self, from: data), 
            response as? HTTPURLResponse // this cast will succeed if the request is an HTTP request
        )
    }
    return try await decoded.value
}
let request: Request&lt;LoginResponse&gt; = .postLogin(email: email, password: password)
let (decodedData, response) = try await URLSession.shared.decode(request)
if let httpResponse = response, httpResponse.statusCode &gt;= 400 {
    // handle error with decodedData...
}

If you want to handle it in the catch block instead, you can check this in the task, and throw an error instead.

func decode&lt;Value: Decodable, Failure: Error &amp; Decodable&gt;(
    _ request: Request&lt;Value&gt;,
    using decoder: JSONDecoder = .init(),
    errorResponseType: Failure.Type
) async throws -&gt; Value {
    let decoded = Task.detached(priority: .userInitiated) {
        let (data, response) = try await self.data(for: request.urlRequest)
        try Task.checkCancellation()
        if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode &gt;= 400 {
            throw try decoder.decode(errorResponseType, from: data)
        }
        return try decoder.decode(Value.self, from: data)
    }
    return try await decoded.value
}
// these could be the same type, but I prefer separating errors from successful responses
struct LoginResponse: Decodable {
    let token: String
    let msg: String
}

struct LoginError: Decodable, Error {
    let token: String
    let msg: String
}
do {
    let request: Request&lt;LoginResponse&gt; = .postLogin(email: email, password: password)
    let decodedData = try await URLSession.shared.decode(request, errorResponseType: LoginError.self)
} catch let error as LoginError {
    // handle login error...
} catch {
    // handle other errors...
}

huangapple
  • 本文由 发表于 2023年7月13日 10:33:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76675537.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定