Swift async/await equivalent of Promise Kit "when" pattern
Asked Answered
F

1

7

I'm new to Swift, coming from JS, and I've started to build an iOS app.

Initially I went down the road, using Promise Kit for the async stuff, as it seemed easier to me than other things I read about.

Regardless, in JS, I use the following pattern a lot:

async function doAyncFunction(item) {
  try {
    // do async call to fetch data using item
    return Promise.resolve(data);
  } catch (error) {
    return Promise.reject(error);
  }
}
const promises = items.map((item) => doAyncFunction(item));
const results = await Promise.all(promises);

And I ultimately got this working with Promise Kit with something like this:

func doManyAsyncRequests(userIds: [String], accessToken: String) -> Promise<Void> {
  Promise { seal in
    let promises = spotifyUserIds.map {
      doSingleAsyncRequest(userId: $0.id, accessToken: accessToken) // this function returns a promise
    }
    when(fulfilled: promises).done { results in
      print("Results: \(results)")
      // process results
    }.catch { error in
      print("\(error)")
      // handle error
    }
  }
}

Promise Kit's when is similar to JavaScript's Promise.all() in that once the promises are fulfilled, things are triggered to move along in the code.

As my learning curve is slow enough, I've decided to start coding for iOS 15 and use Swift async/await.

QUESTION: What Swift async/await pattern that will do the above, similar to Promise Kit's wait and JavaScript's Promise.all()?

Thanks.

UPDATE: Thanks to @workingdog, who helped me arrive at the solution below. I now gotta work on error handling, but that's a different topic for now.

func getAllThings(users: [User], accessToken: String) async -> [Thing] {
    var allThings: [Thing] = []
    await withTaskGroup(of: [Thing].self) { group in
        for user in users {
            group.async {
                let userThings = await self.getUsersThings(
                    accessToken: accessToken,
                    displayName: user.displayName,
                    userId: user.id
                )
                return userThings
            }
        }
        for await (userThings) in group {
            allThings = allThings + userThings
        }
    }
    return allThings
}
Fetlock answered 13/7, 2021 at 23:14 Comment(0)
L
3

you are probably looking for withTaskGroup(...), such as:

func getAll() async {
    await withTaskGroup(of: Void.self) { group in
        await getPosts()
        for post in posts {
            group.async { await self.getCommentsFor(post: post) }
        }
    }
}

I have setup my own basic test to learn this, on github: https://github.com/workingDog/TestAsync

Edit:

This how I would return an array of posts with their comments. As you can see not as neat as getAll().

func getAllPosts() async -> [Post] {
    // this is the tricky parameter bit, the tuple returned when you call group.async {...}
    await withTaskGroup(of: (Int, [Comment]).self) { group in
        // get all the posts
        var thePosts: [Post] = await fetchThem()
        // for each post get all the comments (concurrently)
        for post in thePosts {
            group.async {
                let comments: [Comment] = await self.fetchThem(with: post)
                return (post.id, comments)
            }
        }
        // add the comments to their corresponding post (concurrently)
        for await (postid, comnts) in group {
            if let ndx = thePosts.firstIndex(where: {$0.id == postid}) {
                thePosts[ndx].comments = comnts
            }
        }
        // when all done, return all post with their comments all cooked up
        return thePosts
    }
}
Lemkul answered 14/7, 2021 at 0:8 Comment(5)
Is the intermediary function necessary?Fetlock
Glad it helped. Note, I'm no expert at all. It's all new to me too, and I'm still learning. I did try without the "intermediate" function, but could not make it work.Lemkul
Yeah, me too. This is tricky. What I'd like to do is (using your example) return resolved results straight from getAll()Fetlock
I've edited my answer, with a possible way to return an array of posts directly.Lemkul
Excellent, workingdog! It was the for await bit I was throwing me for a loop. I've marked your answer as the solution and added my final (based on your help) to my original edited question. Thx!Fetlock

© 2022 - 2024 — McMap. All rights reserved.