How to call an async function using async let 如何使用 async let 调用 async 函数

How to call an async function using async let 如何使用 async let 调用 async 函数

温馨提示:本文最后更新于2025-02-07 10:14:09,某些文章具有时效性,若有错误或已失效,请在下方留言

Sometimes you want to run several async operations at the same time then wait for their results to come back, and the easiest way to do that is with async let. This lets you start several async functions, all of which begin running immediately – it’s much more efficient than running them sequentially.
有时你想同时运行几个异步操作,然后等待它们的结果回来,最简单的方法是使用 async let。这样,您就可以启动多个异步函数,所有这些函数都会立即开始运行 – 这比按顺序运行它们要高效得多。

A common example of where this is useful is when you have to make two or more network requests, none of which relate to each other. That is, if you need to get Thing X and Thing Y from a server, but you don’t need to wait for X to return before you start fetching Y.
这有用的一个常见示例是,当您必须发出两个或多个网络请求时,这些请求彼此都不相关。也就是说,如果你需要从服务器获取 Thing X 和 Thing Y,但不需要等待 X 返回后再开始获取 Y。

To demonstrate this, we could define a couple of structs to store data – one to store a user’s account data, and one to store all the messages in their inbox:
为了演示这一点,我们可以定义几个结构体来存储数据 —— 一个用于存储用户的账户数据,另一个用于存储收件箱中的所有消息:

struct User: Decodable, Identifiable {
    let id: UUID
    let name: String
    let age: Int
}

struct Message: Decodable, Identifiable {
    let id: Int
    let from: String
    let message: String
}

These two things can be fetched independently of each other, so rather than fetching the user’s account details then fetching their message inbox we want to get them both together.
这两件事可以彼此独立地获取,因此我们希望将它们放在一起,而不是获取用户的帐户详细信息,然后 获取他们的消息收件箱。

In this instance, rather than using a regular await call a better choice is async let, like this:
在这种情况下,与其使用常规的 await 调用,不如选择 async let,如下所示:

func loadData() async {
    async let (userData, _) = URLSession.shared.data(from: URL(string: "https://hws.dev/user-24601.json")!)

    async let (messageData, _) = URLSession.shared.data(from: URL(string: "https://hws.dev/user-messages.json")!)

    // more code to come
}

That’s only a small amount of code, but there are three things I want to highlight in there:
这只是一小部分代码,但我想强调三点:

  • Even though the data(from:) method is async, we don’t need to use await before it because that’s implied by async let.
    即使 data(from:) 方法是异步的,我们也不需要在它之前使用 await,因为这是 async let 所暗示的。
  • The data(from:) method is also throwing, but we don’t need to use try to execute it because that gets pushed back to when we actually want to read its return value.
    data(from:) 方法也会抛出,但我们不需要使用 try 来执行它,因为它会被推回我们真正想要读取其返回值的时候。
  • Both those network calls start immediately, but might complete in any order.
    这两个网络调用都会立即开始,但可能会按任何顺序完成。

Okay, so now we have two network requests in flight. The next step is to wait for them to complete, decode their returned data into structs, and use that somehow.
好了,现在我们有两个正在进行的网络请求。下一步是等待它们完成,将返回的数据解码为 structs,并以某种方式使用它。

There are two things you need to remember:
您需要记住两件事:

  • Both our data(from:) calls might throw, so when we read those values we need to use try.
    我们的两个 data(from:) 调用都可能引发,因此当我们读取这些值时,我们需要使用 try
  • Both our data(from:) calls are running concurrently while our main loadData() function continues to execute, so we need to read their values using await in case they aren’t ready yet.
    我们的两个 data(from:) 调用同时运行,而我们的主 loadData() 函数继续执行,因此我们需要使用 await 读取它们的值,以防它们还没有准备好。

So, we could complete our function by using try await for each of our network requests in turn, then print out the result:
因此,我们可以依次对每个网络请求使用 try await 来完成我们的函数,然后打印出结果:

struct User: Decodable, Identifiable {
    let id: UUID
    let name: String
    let age: Int
}

struct Message: Decodable, Identifiable {
    let id: Int
    let from: String
    let message: String
}

func loadData() async {
    async let (userData, _) = URLSession.shared.data(from: URL(string: "https://hws.dev/user-24601.json")!)

    async let (messageData, _) = URLSession.shared.data(from: URL(string: "https://hws.dev/user-messages.json")!)

    do {
        let decoder = JSONDecoder()
        let user = try await decoder.decode(User.self, from: userData)
        let messages = try await decoder.decode([Message].self, from: messageData)
        print("User \(user.name) has \(messages.count) message(s).")
    } catch {
        print("Sorry, there was a network problem.")
    }
}

await loadData()

The Swift compiler will automatically track which async let constants could throw errors and will enforce the use of try when reading their value. It doesn’t matter which form of try you use, so you can use trytry? or try! as appropriate.
Swift 编译器将自动跟踪哪些 async let 常量可能会引发错误,并在读取其值时强制使用 try。使用哪种形式的 try 并不重要,因此您可以根据需要使用 trytry? 或 try!

Tip: If you never try to read the value of a throwing async let call – i.e., if you’ve started the work but don’t care what it returns – then you don’t need to use try at all, which in turn means the function running the async let code might not need to handle errors at all.
提示:如果你从未尝试读取抛出的 async let 调用的值——即,如果你已经开始了工作但不关心它返回什么——那么你根本不需要使用 try,这反过来意味着运行 async let 代码的函数可能根本不需要处理错误。

Although both our network requests are happening at the same time, we still need to wait for them to complete in some sort of order. So, if you wanted to update your user interface as soon as either user or messages arrived back async let isn’t going to help by itself – you should look at the dedicated Task type instead.
尽管我们的两个网络请求同时发生,但我们仍然需要等待它们以某种顺序完成。因此,如果您想在usermessages返回后立即更新用户界面,则 async let 本身不会提供帮助 – 您应该查看专用的 Task 类型。

One complexity with async let is that it captures any values it uses, which means you might accidentally try to write code that isn’t safe. Swift helps here by taking some steps to enforce that you aren’t trying to modify data unsafely.
async let 的一个复杂性是它会捕获它使用的任何值,这意味着你可能会不小心尝试编写不安全的代码。Swift 通过采取一些措施来强制您不会尝试不安全地修改数据,从而为您提供帮助。

As an example, if we wanted to fetch the favorites for a user, we might have a function such as this one:
例如,如果我们想为用户获取收藏夹,我们可能有一个这样的函数:

struct User: Decodable, Identifiable {
    let id: UUID
    let name: String
    let age: Int
}

struct Message: Decodable, Identifiable {
    let id: Int
    let from: String
    let message: String
}

func fetchFavorites(for user: User) async -> [Int] {
    print("Fetching favorites for \(user.name)…")

    do {
        async let (favorites, _) = URLSession.shared.data(from: URL(string: "https://hws.dev/user-favorites.json")!)
        return try await JSONDecoder().decode([Int].self, from: favorites)
    } catch {
        return []
    }
}

let user = User(id: UUID(), name: "Taylor Swift", age: 26)
async let favorites = fetchFavorites(for: user)
await print("Found \(favorites.count) favorites.")

That function accepts a User parameter so it can print a status message. But what happens if our User struct was created as a class instead? You can see this for yourself if you change the struct to this:
该函数接受 User 参数,以便可以打印状态消息。但是,如果我们的 User 结构体是作为一个类创建的,会发生什么呢?如果你把结构体改成这样,你可以自己看到这一点:

class User: Decodable, Identifiable {
    let id: UUID
    let name: String
    let age: Int

    init(id: UUID, name: String, age: Int) {
        self.id = id
        self.name = name
        self.age = age
    }
}

With that class in place, Swift will issue a rather opaque error message: “Non-sendable type ‘User’ in implicitly asynchronous access to main actor-isolated let ‘user’ cannot cross actor boundary.”
有了这个类,Swift 将发出一个相当不透明的错误消息:“在隐式异步访问主 actor 隔离中,不可发送类型 ‘User’ let ‘user’ 无法跨越 actor 边界。

What it means is that we’re implicitly sending our User instance somewhere else. Behind the scenes, async let works by making an implicit asynchronous task for us; it’s shorthand syntactic sugar to make this code easier to write. Calling fetchFavorites() takes place in that implicit task, so even though it’s not apparent, our code takes our User object and sends it somewhere else – into a different task to do the work.
这意味着我们将 User 实例隐式发送到其他位置。在幕后,async let 的工作原理是为我们创建一个隐式异步任务;它是使此代码更易于编写的简写语法糖。调用 fetchFavorites() 发生在该隐式任务中,因此即使它并不明显,我们的代码也会获取我们的 User 对象并将其发送到其他位置 – 到不同的任务中来完成工作。

Swift doesn’t think it’s safe to do so. This is because class instances are shared, so two different parts of our code might try to write data at the same time.
Swift 认为这样做并不安全。这是因为类实例是共享的,因此我们代码的两个不同部分可能会尝试同时写入数据。

To fix this, we need to mark the class as final, and also tell Swift it’s safe to be sent across different tasks, like this:
要解决这个问题,我们需要将 class 标记为 final,并告诉 Swift 在不同任务之间发送是安全的,就像这样:

final class User: Decodable, Identifiable, Sendable {

Swift will now validate at compile time that the class is safe to send, so make those two small changes is all it takes to make our code work.
Swift 现在将在编译时验证该类是否可以安全发送,因此只需进行这两个小的更改即可使我们的代码正常工作。

Sendable is quite a tricky topic to understand, so I’ll cover it in detail in the very next chapter.
Sendable 是一个相当难以理解的话题,因此我将在下一章中详细介绍它。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享