如何在Firebase请求中顺序执行函数以获取单一Completion结果
解决Firestore异步操作顺序混乱的问题
我明白你遇到的异步顺序问题有多头疼——Firebase的闭包都是异步执行的,原代码里你刚触发一堆getUsername请求,就立刻去遍历用户名数组、调用fetchPosts,这时候那些异步请求根本还没返回结果,数组自然是空的,completion也就提前返回了空值。
下面用DispatchGroup来实现你需要的严格执行顺序,同时避免多次触发completion:
修改后的完整代码
guard let cuid = Auth.auth().currentUser?.uid else { completion([]) // 处理用户未登录的情况,返回空数组或错误 return } let firestoreDatabase = Firestore.firestore() let getUsernameGroup = DispatchGroup() // 跟踪所有getUsername异步操作 let fetchPostsGroup = DispatchGroup() // 跟踪所有fetchPosts异步操作 // 清空之前的数组,避免重复数据(如果是分页场景可跳过,仅初始化时清空) self.userNamesDictFollowingVC.removeAll() self.postList.removeAll() firestoreDatabase.collection("following").document(cuid).getDocument(source: .server) { document, error in if let error = error { print("获取following文档失败: \(error.localizedDescription)") completion([]) return } guard let document = document, document.exists, let userIdsDictionary = document.data() as? [String : Int] else { completion([]) return } // Part 1: 遍历用户ID调用getUsername userIdsDictionary.forEach { (key, _) in getUsernameGroup.enter() // 标记一个异步任务开始 self.getUsername(uid: key) { uName in defer { getUsernameGroup.leave() } // 任务完成后标记结束,defer确保无论是否有值都会执行 // Part 2: 存入用户名数组(过滤空值) if let userName = uName { self.userNamesDictFollowingVC.append(userName) } } } // 等所有getUsername操作完成后,再执行fetchPosts getUsernameGroup.notify(queue: .main) { // Part 3: 遍历用户名调用fetchPosts self.userNamesDictFollowingVC.forEach { uName in fetchPostsGroup.enter() self.fetchPosts(username: uName) { postList in defer { fetchPostsGroup.leave() } // Part 4: 存入帖子数组 if let posts = postList { self.postList.append(contentsOf: posts) } } } // 等所有fetchPosts操作完成后,调用completion返回结果 fetchPostsGroup.notify(queue: .main) { // Part 5: 执行completion(在主线程,方便后续更新UI) completion(self.postList) } } }
关键说明
DispatchGroup的正确用法:
- 每个异步任务开始前调用
enter(),任务结束(无论成功失败)调用leave(),用defer确保leave()一定会执行,避免组永远等待。 notify(queue:)方法会在组内所有任务完成后触发,保证严格的顺序执行。
- 每个异步任务开始前调用
错误处理:
- 增加了获取following文档失败、用户未登录等边界情况的处理,确保
completion总会被调用,避免出现无响应的情况。 - 过滤了
getUsername返回的空值,避免无效的用户名进入后续流程。
- 增加了获取following文档失败、用户未登录等边界情况的处理,确保
分页扩展准备:
- 现在的结构已经支持分页逻辑:你可以在
fetchPosts方法中加入分页参数(比如lastDocument),每次加载更多时,只需要更新postList(而不是清空),并在最后调用completion返回更新后的数组即可。
- 现在的结构已经支持分页逻辑:你可以在
线程安全:
- 所有数组操作和
completion调用都放在主线程(.main队列),如果你的数组是用于UI展示的,这样可以避免线程冲突问题。
- 所有数组操作和
内容的提问来源于stack exchange,提问作者hyasar
相关产品推荐
相关产品推荐

