Как дождаться ответа асинхронной функции в Swift

При запуске следующего кода моя функция возвращает пустой массив для функции fetchProfilesForList.

Это связано с тем, что функция не ожидает завершения выборки профилей перед возвратом.

Как изменить следующий код, чтобы дождаться ответа асинхронной функции?

Модель представления

func addNoteToList(title: String, note: String) {

    if let id = self.listID {

        if let userID = profileID {
            fm.fetchProfile(uid: userID, completion: { [self] profile in
                print("FETCHED USER PROFILE FOR SHORTCUT")
                
                fm.fetchList(uid: id, completion: { [self] list in
                    print("FETCHED LIST FOR SHORTCUT")
                    
                    
                    fetchProfilesForList(list: List, completion: { profiles in
                        print("users retuned \(profiles.count)")

                        for p in profiles {
                            print("ADD NOTE TO LIST SHORTCUT")
                            
                            //Create LIST
                            let note = Payment(
                                title: title, note: note, user: p.uid )
                            
                            //Add payment to home
                            var l = list
                            l.notes.append(note)
                            self.fm.updateList(l)
                            
                        }
                    })
                })
            })
        }
    }
}

func fetchProfilesForList(list: List, completion: @escaping ([Profile]) -> Void) {

    var group: [Profile] = []
                
        if list.admin != profileID && list.group.contains(profileID!) {
            print("IS NOT ADMIN and add to note")
            
            fm.fetchProfile(uid: home.admin, completion: { admin in
                print("FETCHED ADMIN")
                
                people.append(admin)
            })
        }
        
        
        for p in list.group {
            print("P IN GROUP")
            
            if p != profileID! {
                // Fetch profile
                fm.fetchProfile(uid: p, completion: { profile in
                    print("profile \(profile.firstName)")
                    
                    group.append(profile)
                })
            }
        }

    let users = group.removeDuplicates() // incase fetched twice
    
    completion(p) // RETURNS BEFORE PROFILES ARE FETCHED

}

Менеджер пожарного магазина

public class FirestoreManager {

let db = Firestore.firestore()

func fetchList(uid: String, completion: @escaping (Home) -> Void) {
    db.collection("lists").document(uid).getDocument { (document, error) in
        if let document = document, document.exists {
            do {
                let l = try document.data(as: List.self)
                completion(l)
            }
            catch {
                print("There was an error getting decoding List")
            }
            
        } else {
            print("Document for List
 \(uid) does not exist")
        }
    }
}

func fetchProfile(uid: String, completion: @escaping (Profile) -> Void) {
    
    db.collection("users").document(uid).getDocument { (document, error) in
         if let document = document, document.exists {
            do {
                let p = try document.data(as: Profile.self)
                completion(p)
            }
            catch {
              print("There was an error getting decoding Profile")
            }
            
         } else {
            print("Document for users / profile does not exist")
        }
     }
}

Используйте DispatchGroup с group.enter()/group.leave() или замените completion на async/await.

Larme 27.08.2024 18:29

Swift для асинхронного/ожидания. У Firestore есть совместимые версии практически для всего.

lorem ipsum 27.08.2024 22:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это происходит потому, что выполняется несколько вызовов асинхронной выборки. Поскольку это асинхронные вызовы, и завершение основной функции вызывается до того, как запросы выборки вернут данные обратно.

Есть два способа решить эту проблему -

  1. Использование DispatchGroup для ожидания завершения всех асинхронных вызовов перед вызовом завершения основной функции.
  2. Используйте async/await, если ваш проект может поддерживать минимальную необходимую для этого версию.

Использование DispatchGroup –

func fetchProfilesForList(list: List, completion: @escaping ([Profile]) -> Void) {
    var group: [Profile] = []
    let dispatchGroup = DispatchGroup()
    if list.admin != profileID && list.group.contains(profileID!) {
        print("IS NOT ADMIN and add to note")
        // Enters in dispatch group
        dispatchGroup.enter()
        fm.fetchProfile(
            uid: home.admin,
            completion: {
                admin in
                print("FETCHED ADMIN")
                group.append(admin)  // Assuming, you are appending to "group here instead of "people"
                // Leaves the group
                dispatchGroup.leave()
            }
        )
    }
    
for p in list.group {
    print("P IN GROUP")
    if p != profileID! {
        // Fetch profile
        dispatchGroup.enter()
        fm.fetchProfile(
            uid: p,
            completion: {
                profile in
                print("profile \(profile.firstName)")
                group.append(profile)
                dispatchGroup.leave()
            }
        )
    }
}
    
// It gets called only when all the calls leave DispatchGroup
// So it will wait for all the async calls to complete
dispatchGroup.notify(queue: .main) {
    let users = group.removeDuplicates()  // incase fetched twice
    completion(users)  // Assuming you want to return "users" here instead of "p"
}
}

Использование async/await – Айнтаксис async/await естественен и намного чище, чем обратные вызовы. Если вы хотите использовать async/await, вам придется преобразовать функции обратного вызова в асинхронные функции, используя в этом случае либо withContinuation, либо withCheckedContinuation.

Вы можете преобразовать свои функции обратного вызова в асинхронные следующим образом:

func fetchProfile(uid: String) async -> Profile? {
    await withCheckedContinuation { continuation in
        db.collection("users").document(uid).getDocument { (document, error) in
            if let document = document, document.exists {
                do {
                    let profile = try document.data(as: Profile.self)
                    continuation.resume(returning: profile)
                } catch {
                    print("There was an error decoding Profile")
                    continuation.resume(returning: nil)
                }
            } else {
                print("Document for users / profile does not exist")
                continuation.resume(returning: nil)
            }
        }
    }
}

и использовать их вот так

func fetchProfilesForList(list: List) async -> [Profile] {
    var group: [Profile] = []

    if list.admin != profileID && list.group.contains(profileID!) {
        print("IS NOT ADMIN and add to note")
        
        if let admin = await fetchProfile(uid: list.admin) {
            print("FETCHED ADMIN")
            group.append(admin)
        }
    }

    for p in list.group {
        print("P IN GROUP")
        
        if p != profileID!, let profile = await fetchProfile(uid: p) {
            print("profile \(profile.firstName)")
            group.append(profile)
        }
    }

    let users = group.removeDuplicates() // In case fetched twice
    
    return users
}

Другие вопросы по теме