2

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!

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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()

Comments

0

You can use the async keyword in your for statement, like

for friend in friends async throws {

1 Comment

Will that make the looping async, though? My impression was that that syntax still blocks for each iteration of the loop.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.