Reputation: 1197
I have swift code that looks like this:
var jsFriendlyFreinds = [JSObject]()
for friend in friends {
let jsFriend = await FriendsPlugin.createFriendResult(friend)
jsFriendlyFreinds.append(jsFriend)
}
I'd like for all the async calls to happen at the same time, like how javascript's Promise.all
works. For one-offs I know you can use async let
, but I'm not sure how to do this in the context of a loop or map. Thanks!
Upvotes: 2
Views: 1358
Reputation: 438437
The basic idea for parallel loop is to use withTaskGroup
, and then addTask
for each asynchronous call. The trick, though, is that one generally wants to be able to associate the responses with items in the original array (because when running concurrently, you have no assurances as to what order the responses are received). So, you might use a dictionary with the results. E.g., if your Friend
object was Identifiable
, you might do:
func objects(for friends: [Friend]) async -> [Friend.ID: JSObject] {
await withTaskGroup(of: (Friend.ID, JSObject).self) { group in
for friend in friends {
group.addTask { await (friend.id, FriendsPlugin.createFriendResult(friend)) }
}
// build dictionary from array of tuples
var dictionary: [Friend.ID: JSObject] = [:]
while let (index, object) = await group.next() {
dictionary[index] = object
}
// now return array of objects in the order that the friends appeared in the original array
return dictionary
}
}
Or, more concisely:
func objects(for friends: [Friend]) async -> [Friend.ID: JSObject] {
await withTaskGroup(of: (Friend.ID, JSObject).self) { group in
for friend in friends {
group.addTask { await (friend.id, FriendsPlugin.createFriendResult(friend)) }
}
return await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
}
}
Alternatively, if you would simply like a [JSObject]
array:
func objects(for friends: [Friend]) async -> [JSObject] {
await withTaskGroup(of: (Int, JSObject).self) { group in
for (index, friend) in friends.enumerated() {
group.addTask { await (index, FriendsPlugin.createFriendResult(friend)) }
}
// build dictionary from array of tuples
var dictionary: [Int: JSObject] = [:]
while let (index, object) = await group.next() {
dictionary[index] = object
}
// now return array of objects in the order that the friends appeared in the original array
return friends.indices.compactMap { dictionary[$0] }
}
}
Or, again, more concisely:
func objects(for friends: [Friend]) async -> [JSObject] {
await withTaskGroup(of: (Int, JSObject).self) { group in
for (index, friend) in friends.enumerated() {
group.addTask { await (index, FriendsPlugin.createFriendResult(friend)) }
}
let dictionary: [Int: JSObject] = await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
return friends.indices.compactMap { dictionary[$0] }
}
}
There are many variations on the theme, but the idea is to use withTaskGroup
/addTask
to run the requests concurrently and then collate the results into some structure by which you can associate the responses with the items in the original array.
Upvotes: 2
Reputation: 37015
You could try this approach, using await withTaskGroup(...)
,
as shown in this example code, to make all the async calls
happen in parallel.
let friends: [String] = ["friend-1","friend-2","friend-3"]
var jsFriendlyFreinds = [GoodFriend]()
func getFriends() async {
// get all friends in parallel
return await withTaskGroup(of: GoodFriend.self) { group -> Void in
for friend in friends {
group.addTask { await createFriendResult(friend) }
}
for await value in group {
jsFriendlyFreinds.append(value)
}
}
}
// for testing
func createFriendResult(_ friend: String) async -> GoodFriend {
// ....
return GoodFriend(name: "xxx")
}
struct GoodFriend {
var name: String
// ....
}
Use it like this:
await getFriends()
Upvotes: 1
Reputation: 557
You can use the async keyword in your for statement, like
for friend in friends async throws {
Upvotes: 0