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
}
Kim
  • 856
  • 1
  • 11
  • 21

1 Answers1

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
    }
}
  • Is the intermediary function necessary? – Kim Jul 14 '21 at 20:19
  • 1
    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. – workingdog support Ukraine Jul 14 '21 at 22:41
  • Yeah, me too. This is tricky. What I'd like to do is (using your example) return resolved results straight from getAll() – Kim Jul 14 '21 at 23:48
  • 1
    I've edited my answer, with a possible way to return an array of posts directly. – workingdog support Ukraine Jul 15 '21 at 01:44
  • 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! – Kim Jul 15 '21 at 17:44