You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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)
        }
    }
}

关键说明

  1. DispatchGroup的正确用法:

    • 每个异步任务开始前调用enter(),任务结束(无论成功失败)调用leave(),用defer确保leave()一定会执行,避免组永远等待。
    • notify(queue:)方法会在组内所有任务完成后触发,保证严格的顺序执行。
  2. 错误处理:

    • 增加了获取following文档失败、用户未登录等边界情况的处理,确保completion总会被调用,避免出现无响应的情况。
    • 过滤了getUsername返回的空值,避免无效的用户名进入后续流程。
  3. 分页扩展准备:

    • 现在的结构已经支持分页逻辑:你可以在fetchPosts方法中加入分页参数(比如lastDocument),每次加载更多时,只需要更新postList(而不是清空),并在最后调用completion返回更新后的数组即可。
  4. 线程安全:

    • 所有数组操作和completion调用都放在主线程(.main队列),如果你的数组是用于UI展示的,这样可以避免线程冲突问题。

内容的提问来源于stack exchange,提问作者hyasar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 18:15:36