一个 Swift 库,用于处理异步和同步错误,无需 try
和 catch
代码块。
将 MightFail 作为依赖项添加到你的 Package.swift
文件
dependencies: [
.package(url: "https://github.com/might-fail/swift.git", from: "0.1.0")
]
然后将其添加到你的目标依赖项中
targets: [
.target(
name: "YourTarget",
dependencies: ["MightFail"]),
]
在你的源文件中导入 MightFail
import MightFail
MightFail 提供了一种简化的方式来处理 Swift 中的错误,无需传统的 try-catch 代码块。它适用于同步和异步代码。
// Good
guard let data else {
// handle error
}
// Good
guard success else {
// handle error
}
// Bad
if let error {
// handle error
}
// Returns (error, result, success)
let (error, result, success) = mightFail {
return "Success"
}
// Check success
if success {
print(result) // "Success"
}
// Returns just (error, result)
let (error, result) = mightFail {
return 42
}
print(result) // 42
print(error) // nil
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
print("Success! Yum.")
} catch VendingMachineError.invalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
} catch {
print("Unexpected error: \(error).")
}
let vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
let (error, _, success) = mightFail {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
}
guard success else {
switch error {
case VendingMachineError.invalidSelection:
print("Invalid Selection.")
case VendingMachineError.outOfStock:
print("Out of Stock.")
case VendingMachineError.insufficientFunds(let coinsNeeded):
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
default:
print("Unexpected error: \(error).")
}
return
}
print("Success! Yum.")
// Basic async usage
let (error, result, success) = await mightFail {
try await Task.sleep(nanoseconds: 1_000_000)
return "Async Success"
}
// Simplified async return
let (error, result) = await mightFail {
try await Task.sleep(nanoseconds: 1_000_000)
return 42
}
你可以运行多个操作并获取它们的结果
let results = mightFail([
{ 1 },
{ throw TestError.simple },
{ 3 },
])
// Check results
results.forEach { (error, result) in
guard let result else {
print("Error: \(error)")
return
}
print("Result: \(result)")
}
或者可能是这样的
guard let imageFiles = await memoryStorage.imageFilesStore[imageId] else {
return
}
let deleteResults = mightFail([
{ try FileStorage.deleteFile(name: imageFiles.fullSizeName, ext: imageFiles.ext) },
{ try FileStorage.deleteFile(name: imageFiles.thumbnailName, ext: imageFiles.ext) },
{ try FileStorage.deleteFile(name: imageFiles.mediumSizeName, ext: imageFiles.ext) },
])
for deleteResult in deleteResults.filter({ $0.success == false }) {
print("Failed to delete image file: \(deleteResult.error)")
}
for deleteResult in deleteResults.filter({ $0.success == true }) {
print("Deleted image file: \(deleteResult.result)")
}
MightFail 优雅地处理可选值
func returnOptional() throws -> String? {
return nil
}
let (error, result, success) = mightFail {
try returnOptional()
}
// success will be true
// result will be nil
// error will be nil
我认为抛出异常很好,我喜欢异常会中断控制流,我也喜欢异常传播。我唯一不喜欢的是捕获异常。
这通常发生在代码中最“面向用户”的部分,例如 API 端点或 UI 组件,即最外层的函数调用。因此,捕获异常需要通知用户发生了错误,记录错误以进行调试,并停止当前的执行流程。
Guarding 允许你尽早处理错误并从函数中提前返回,使其更具可读性且更易于理解。
// Generic fetch function that can work with any Codable type
func fetch<T: Codable>(from urlString: String) async throws -> Data {
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}
// Create and configure the URL session
let session = URLSession.shared
// Make the network request and await the response
let (data, response) = try await session.data(from: url)
guard let data = data else {
throw URLError(.badServerResponse)
}
// Verify we got a successful HTTP response
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
}
现在,成功情况是唯一没有嵌套在 if
或 guard
语句中的代码。它也位于函数的底部,使其易于查找。
但由于某些原因,当我们调用此函数时,我们认为可以随意抛弃 guard 语句的所有优点。
do {
let data = try await networkManager.fetch(
from: "https://jsonplaceholder.typicode.com/posts/1"
)
// success case in the middle somewhere and nested
} catch {
print("Error fetching post: \(error)")
}
然后这导致将大量代码放在 do
代码块中。
try {
let data = try await networkManager.fetch(
from: "https://jsonplaceholder.typicode.com/posts/1"
)
// Decode the JSON data into our Codable type
let decoder = JSONDecoder()
let post = try decoder.decode(Post.self, from: data)
try post.data.write(toFile: "path/to/newfile.txt", atomically: true, encoding: .utf8)
print("Successfully fetched, decoded, and saved the post")
} catch (error) {
// handle any errors, not sure which one though 🤷♀️
}
// or
catch let error as URLError {
print("Network error: \(error.localizedDescription)")
} catch let error as DecodingError {
print("JSON error: \(error.localizedDescription)")
} catch let error as CocoaError {
print("File error: \(error.localizedDescription)")
} catch {
print("Unexpected error: \(error)")
}
这是不好的,因为
// First try-catch for network request
let data: Data
do {
data = try await networkManager.fetch(
from: "https://jsonplaceholder.typicode.com/posts/1"
)
} catch let error as URLError {
print("Network error: \(error.localizedDescription)")
return // or throw, or handle error differently
} catch {
print("Unexpected network error: \(error)")
return
}
// Second try-catch for JSON decoding
let post: Post
do {
let decoder = JSONDecoder()
post = try decoder.decode(Post.self, from: data)
} catch let error as DecodingError {
print("JSON decoding error: \(error.localizedDescription)")
return
} catch {
print("Unexpected decoding error: \(error)")
return
}
// Third try-catch for file writing
do {
try post.data.write(toFile: "path/to/newfile.txt", atomically: true, encoding: .utf8)
} catch let error as CocoaError {
print("File writing error: \(error.localizedDescription)")
return
} catch {
print("Unexpected file error: \(error)")
return
}
// If we get here, everything succeeded
print("Successfully fetched, decoded, and saved the post")
这可能是处理错误的一种更好的方法,但没有人会像这样编写三个 do catch 代码块。而且 catch 不如 guard 好,因为它不会强制你处理错误并提前返回。
Guarding 是好的,错误处理应该在 guard 中紧挨着导致错误的代码进行处理。成功情况的代码应该在 guard 错误处理之后。
我们已经知道这一点了,现在让我们用抛出异常的代码来这样做。
// First try-catch for network request
let (networkError, data) = await mightFail { try await networkManager.fetch(
from: "https://jsonplaceholder.typicode.com/posts/1")
}
guard let data else {
switch networkError {
case URLError.badURL:
print("Bad URL")
default:
print("Network error: \(networkError)")
}
return // or throw, or handle error differently
}
// Second try-catch for JSON decoding
let decoder = JSONDecoder()
let (decodingError, post) = await mightFail {
try decoder.decode(Post.self, from: data)
}
guard let post else {
switch decodingError {
case DecodingError.keyNotFound:
print("Key not found")
default:
print("Decoding error: \(decodingError)")
}
return
}
// Third try-catch for file writing
let (fileError, _, success) = await mightFail {
try post.data.write(toFile: "path/to/newfile.txt", atomically: true, encoding: .utf8)
}
guard success else {
switch fileError {
case CocoaError.fileWriteNoPermission:
print("No permission to write file")
default:
print("File writing error: \(fileError)")
}
return
}
// If we get here, everything succeeded
print("Successfully fetched, decoded, and saved the post")